Navigate to # id in Layout component - javascript

I'm using Gatbsy and I'm trying to create an accessibility link for the users (using screen readers) to be able to navigate to content (skipping the navigation).
My layout component (which is used in every page of the website) looks something like this:
const Layout = ({ children }) => {
{/* hidden for brevity */}
return (
<>
Skip to content {/* <-reference line */}
{/* hidden for brevity */}
<main id="main-content">
{children}
</main>
{/* hidden for brevity */}
</>
);
}
Current behaviour:
As it's shown in the code example above the link will be generated once for the page I'm "landing on", and it will not update for subsequent pages I navigate to:
ex. landing on the 'blog' page my link will point to localhost:9000/blog#main-content and navigating to 'about' page will not make that section of the layout re-render (this is most likely normal behaviour).
I also tried using useRef, but got the referenced item being undefined during rendering.
What would be the way to complete this task (considering that the pages are server-side-rendered)? Is there a way I could do it without passing the full route path to use it inside the tag?

As I commented above, Gatsby's <Link> component extends from #reach/router (from React) doesn't allow navigating to any parameter. From the docs:
Neither <Link> nor navigate can be used for in-route navigation with a
hash or query parameter. If you need this behavior, you should either
use an anchor tag or import the #reach/router package—which Gatsby
already depends upon—to make use of its navigate function.
If you use navigate, for example, navigate("/blog#main-content") it will redirect to /blog, omitting the parameter since it's not allowed.
In your case, the useRef approach won't directly since at the moment you are creating it, it's not still rendered so, you can use the useRef hook approach alongside with useEffect hook, to ensure that the DOM tree is loaded or using some manual trigger:
const Layout = ({ children }) => {
const mainRef= useRef(null);
const navigateToContent= () => {
mainRef.current.scrollIntoView(); //manual trigger
}
useEffect(()=>{
mainRef.current.scrollIntoView(); //automatic trigger
}, [])
{/* hidden for brevity */}
return (
<>
<div onClick={navigateToContent}>Skip to content</div>
{/* hidden for brevity */}
<main id="main-content" ref={mainRef}>
{children}
</main>
{/* hidden for brevity */}
</>
);
}
In the snippet above I've added two different approaches, choose the one that fits your requeriements. The key part is to set correctly the reference of the main tag, initially set as null to avoid rehydration issues when the routing changes.
The manual trigger, just call a function (navigateToContent) in your Skip to content
element that uses the scrollIntoView() built-in function to scroll to the reference.
The automatic trigger, just uses the same idea but the function is triggered once the DOM tree is loaded (useEffect with empty deps, []).
The issue has been solved as following:
Actually changing this <Link to="/#main-content">Skip to content</Link> to this <Link to="#main-content">Skip to content</Link>
(removing the /) works perfectly fine

Related

How to force tabs to mount without ever clicking on the tab?

Using React & material-ui, I have a pretty big tab container and want to keep data fetches local to each Tab component. I want to be able to essentially greedy load some of the Tab components so as soon as the Tab container is mounted, the Tabs with a greedyLoad prop passed to them are mounted (although not the active tab/visible) and make the fetch for the data they need.
The reason is some of the tabs need a count from the data I fetch in the tab label.
I understand I can fetch the data from the parent component and pass the data as a prop downwards, but I really would like to keep the fetch’s local to each tab component. I’ve seen it done at a previous company I worked at and totally forgot how it worked. Something with CSS I think. Thanks in advance
If you hide the component with CSS, your component will mount on the DOM, but it will be invisible to the user. We just need to add some inline css and make use of the display: none property
function myComponent(show) {
// TODO: fetch the data
return (
<div style={{display: show ? "block" : "none"}}>
<h1 >This component may be invisible!</h1>
<p>{data}</p>
</div>
);
}

append material ui snackbar to body

I have a component that is only visible when a user hovers over it. In that component I have a button which allows the user to add something to the local storage. If the button is clicked the component is removed from the DOM. This works fine, but I want to show the user a toast when the action is completed. The issue is that the toast is also removed when the button is clicked because it's part of that component:
return (
<React.Fragment>
<Overlay backdrop_path={movie.backdrop_path}>
<div>
<AddMovie onClick={() => addMovie(movie)}>Add movie to your watchlist</AddMovie>
</div>
</Overlay>
<Snackbar
open={open}
onClose={handleClose}
TransitionComponent={Slide}
message="Movie has been added"
/>
</React.Fragment>
)
I rather not put the Snackbar toast in a different component because this component is responsible for adding a movie to the local storage, and I don't want to do a lot of props lifting etc to get the result.
So I thought, maybe it's possible to append the Snackbar element to the body instead of the components element. This way if the components element is removed the Snackbar should still be visible. Not sure if this logic will actually work though.
Is it possible to append a element/component to another part of the DOM structure, if so: how?
Seems appending rendered elements into other DOM elements isn't difficult:
return ReactDOM.createPortal(
<Snackbar
open={true}
onClose={handleClose}
TransitionComponent={Slide}
message="Movie has been added"
/>,
document.body
);
But this element also gets removed the instant the component that renders it is removed. Which makes sense. So it looks I need to render the Snackbar in a different element. Shame.
// edit. By using Redux it might be possible to create a global state for using the Snackbar elements > https://browntreelabs.com/snackbars-in-react-redux-and-material-ui/
// edit#2. https://github.com/iamhosseindhv/notistack this package makes it a lot easier and is supported by the Material UI team.

What is the best approach to build a react app with a large amount of component pages?

I've been tasked with building an application in ReactJS that will have roughly 70 - 100 pages / subpages. So far I've tried building this so each page is it's own component i.e. mainPage.js , subPage1.js, subPage2.js etc. but in the long run there will be far too many pages to maintain. Many of the pages follow the same structure like the code below, but each page will of course have it's own unique data.
<Header>
<Main Content>
<Footer>
So my question is, is there a way to dynamically create pages in React or a way of creating component pages so that I don't have to create a seperate page for every single different page?
Use react component composition. Sounds like you've already some header, content, footer components. You should categorize the types of pages your app uses and create general purpose container components for each use-case.
For example, a "base" or "first-level" page component could look like:
const Page = ({ pageHeaderProps, pageFooterProps, ...props }) => (
<Page.Container>
<Page.Header {...pageHeaderProps} />
<Page.Content {...props} />
<Page.Footer {...pageFooterProps} />
</Page.Container>
);
Here the children prop is spread into the (or wrapped by) Content component.
If for example some sub-pages have an additional sub-header or some other common component, then you can compose a component as:
const SubPage = ({ children, subHeaderProps, ...props }) => (
<Page {...props}>
<SubHeader {...subHeaderProps} />
{...children}
</Page>
);
Here the new SubHeader is injected as a child of the Page and will be rendered as part of all the children it renders.

Link page change not triggering animations

I am currently been for 2 days now attempting to get react-reveal to work where it does not require a refresh.
Currently what is happening is when I change page via Link it does not trigger some of the components to animate until I refresh the page.
I have read the documentation and it suggests:
refProp: string If you're using react-reveal component with a custom
React component (like a NavLink from React Router) you must specify
prop name that would be allow access to the DOM. Typically it would be
something like innerRef. By default it is ref. Optional.
So I place in my header.js
let anchorRef = React.createRef();
<Link to="/about" innerRef={anchorRef}>
Then on the about.js page I am getting undefined when I do
constructor(props) {
super(props);
console.log(props.innerRef);
}
This is what I am returning on the page as well as importing the library and etc
<Fade bottom ssrReveal distance="150px">
<p>This is an example</p>
</Fade>
All I am after doing is correctly implementing as suggested in the documents however it would seem I am missing something as I am new to using react.
This is the animation library https://www.react-reveal.com/docs/props/

react link vs a tag and arrow function

I just started on react router.
I have two questions. What is the difference between using <Link to="/page"> and <a href="page">? Both make the exact same get request to /page but I get an error when I use <a href="page"> but it works when I use <Link to="/page"> when I am nesting routes. I don't understand, how there could be any difference, when I know for fact that both render to exact same url?
Second is the weird arrow function in react router v4 documentation
const About = () => (
<div>
<h2>About</h2>
</div>
)
I know () => {} these are new in ES6 but I cannot find anything on normal brackets instead of parentheses. What are they?
Edit
My index.js class (I have all the imports)
render((
<Router>
<div>
<Route component={App}/>
</div>
</Router>
), document.getElementById('root')
);
My App.js class
class App extends Component {
render() {
return (
<div className="container">
<header>
<span className="icn-logo"><i className="material-icons">code</i></span>
<ul className="main-nav">
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/teachers">Teachers</Link></li>
<li><Link to="/courses">Courses</Link></li>
</ul>
</header>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
<Route path="/teachers" component={Teachers}/>
<Route path="/courses" component={Course}/>
</div>
);
}
}
export default App;
The error I'm getting.
Cannot GET /about on the browser when I try to move to localhost:8080/about. However, when I click the about button, it goes to exactly the same url /about and renders perfectly
This may be a bit late to address your issue and you may well have figured it out. But here's my take:
First:
What is the difference between using <Link to="/page"> and <a
href="page">
On the surface, you seem to be comparing apples and oranges here. The path in your anchor tag is a relative path while that one in the Link is absolute (rightly so, I don't think react-router supports relative paths yet). The problem this creates is say you are on /blah, while clicking on your Link will go to /page, clicking on the <a href='page' /> will take you to /blah/page. This may not be an issue though since you confirmed the correctness of the url, but thought to note.
A bit deeper difference, which is just an addon to #Dennis answer (and the docs he pointed to), is when you are already in a route that matches what the Link points to. Say we are currently on /page and the Link points to /page or even /page/:id, this won't trigger a full page refresh while an <a /> tag naturally will. See issue on Github.
A fix I used to solve my little need around this was to pass in a state property into link like so <Link to={{pathname: "/page", state: "desiredState"}}>Page</Link>. Then I can check for this in the target component's (say <Page />) componentWillReceiveProps like so:
componentWillReceiveProps(nextProps){
if (nextProps.location.state === 'desiredState') {
// do stuffs
}
}
Second question:
the weird arrow function in react router v4 documentation... I cannot find anything on normal brackets instead of parentheses. What are they?
Arrow functions; again #Dennis and #Jaromanda X have kind of addressed it. However, I've got three bits to add:
When you have () => blah without the curly braces {}, you are implicitly returning whatever follows the => in this case blah. But when you have curly braces immediately after the arrow, then it's now your responsibility to return something if you so desire. So () => blah (which by the way is synonymous to () => (blah)) will be more similar to () => { return blah } and not () => { blah }.
So what happens if you want to return an object: { blah: blah }; this is what #Jaromanda X was pointing at. You will then need to do () => ({ blah: blah }) or simply () => ({ blah }) for implicit return or you could return explicitly like so () => { return { blah: blah } }.
My third bit is to point you to MDN
Hope it helps.
The href attribute would trigger a page refresh which would reset the application states. However the link and navlink of react-router doesn't trigger a page refresh. Since React is used to create single page applications most of the time make sure you choose Link or Navlink when working with routing
The component allows you to do more than the normal link element. For instance, because it's a React component you have the benefits of having a state and what not (if you want that). You can see more documentation on here. Without the error I'm not sure what happens, but I suspect the routing library wants you to use the component, over a normal html element.
With regards to () => {} this is a construct which is called an anonymous function, or a lambda expression. It's basically the same as saving a function in a variable: var x = function(){ return (<div>...) }; if you have anything in the first parenthesis, it's a parameter which you have access to: const x = (y) => return y*2; The reason it's done in React is to expose the function scope to the component it lies in.
There is no better then looking at the code source.
https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/modules/Link.js
You can see that Link is a component, that internally use history. Which is the module|library behind the history and navigation for react-router. And come with different modes (in memory history, browserHistory, hashHistory. And even custom).
Yea as a similarity it render an anchor tag but the default behavior is overridden (preventDefault()). They could have used just a div. But not completely right. As for the reason bellow.
So basically it work like that:
Observe the condition bellow
if (
!event.defaultPrevented && // onClick prevented default
event.button === 0 && // ignore everything but left clicks
(!this.props.target || this.props.target === "_self") && // let browser handle "target=_blank" etc.
!isModifiedEvent(event) // ignore clicks with modifier keys
) {
}
if the condition above is met. It will use history (push or replace). Otherwise it will leave the browser normal behavior. And so in that case it will be just a normal anchor tag <a />. Example letting the browser handle target='blank'. The condition are well explained.
Then depending on the type of history object. The behavior change. Not the behavior of ` itself. But just the result of the history object type.
In resume:
<Link /> is a component, that render a <a /> anchor tag. However in the main conditions the default behavior is prevented (preventDefault()). That allow it to apply the change to the history object (onClick event). Which react-router navigation is based on. And on the some conditions as mentioned above. It just fall back to the browser behavior. And just be exactly a <a /> anchor tag (no preventDefault()).
For the use. If you are using React-router. Then you just need to use Link.

Categories