Navigation within a React app
As with any website, I wanted to add a navigation menu to my personal website (built with ReactJS) when I was developing it — https://ahaydar.github.io
In this post, I will go through my thought process and document my learnings about the tech used. I hope this will be helpful for anyone starting with React and React Router.
The navigation menu includes the following items:
- The “home” menu item gives accessibility to the current personal site
- The “now” menu item takes the user to a new page where I list what I am currently doing (inspired by “Now” page, which I learned about in an episode of the knowledge project)
- The “work” menu item enables the navigation to the second section of the same page related to work I’ve done (hoping to extend this to a page talking about how I work, and what I’ve worked on in the past)
- The “blog” menu item to enable accessing my external blog hosted on https://ali-haydar.medium.com/.
As I started with the implementation, The first thought that came to mind is to use the Anchor element: <a> to implement this navigation:
<a href="">home</a>
<a href="/now">now</a>
<a href="#myWork">work</a>
<a href="https://medium.com/@ali.haydar">blog</a>
But there are a few problems here.
What will happen if we click on the “now” menu item? Notice how the url changed to http://localhost:3000/now
, but no navigation happened and we stayed on the same page. How can we make it go to the new page (assuming we have created a new page that contains the "now" content).
Googling how to render a new React component by clicking a HTML anchor shows lots of possibility, and some of the results showed the <link> element. <link> is mainly used in the <head> part of the HTML document to link an external file to the document (e.g. a css file). <a> is used to display a hypertext reference link. So using the HTML <link> is not an option for us here.
Other possibilities include handling the click on the “Anchor” element to render the new component — that’s lots of things to manage, and it kinda defeats the purpose of using an anchor.
Here comes React router to take away a bit of that complexity. Time to learn how it works.
We have multiple use cases:
- A link that takes us to the main page (home)
- A link that takes us to another route (now), but maybe we want to keep the header
- A link that takes us to a section of the main page (work)
- A link that takes us to an external website (blog)
Routing to the “Now” page
We will start with working on the link that takes us to the “now” page, but before that, let us explore React Router a bit. React Router is mainly a react component that will not display anything until we define a router. The routers will have defined routes, and as the user clicks around on different <Link> tags, the router renders the matching route.
Let us build our navigation. First import BrowserRouter, Route and Link from react-router-dom
, then use <Link> tag instead of <a> (Behind the scenes <Link> uses the anchor tag with a href).
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
//React componentconst NavMenu = () => (
<Router>
<ul>
<li>
<Link to="/">home</Link>
</li>
<li>
<Link to="/now">now</Link>
</li>
<li>
<Link to="/#myWork">work</Link>
</li>
<li>
<Link to="https://medium.com/@ali.haydar">blog</Link>
</li>
</ul>
</Router>
)
By clicking around on the above link, no behaviour will happen on the browser, except the change in the URL, to reflect the value listed in the “to” attribute.
Now we would need to add the routes, which will do the navigation once the correspondent <Link> is clicked.
We need to add a <Switch> that looks through its children <Route>s and renders the first one that matches the current URL. Let us start by adding a route for the “Now” page. The code will look like that (assuming we have built the Now component):
import { BrowserRouter as Router, Switch, Route, Link } from 'react-router-dom';
import Now from './Now';const NavMenu = () => (
<Router>
<ul>
<li>
<Link to="/">home</Link>
</li>
<li>
<Link to="/now">now</Link>
</li>
<li>
<Link to="/#myWork">work</Link>
</li>
<li>
<Link to="https://medium.com/@ali.haydar">blog</Link>
</li>
</ul>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/now" component={Now} />
</Switch>
</Router>
)
That’s mainly it, we just covered routing to a new page with React Router.
As React router is mainly a react component, if it was needed to have the route change all components except for the header and footer, the code will look like this:
const ParentComponent = () => (
<Router>
<Header />
<Switch>
<Route exact path="/" component={Home} />
<Route path="/now" component={Now} />
</Switch>
<Footer />
</Router>
)
Link to a section in the home page
Have you noticed that the “work” link is no longer working? How can we configure React router to keep us on the same page but “scroll” to a section with a certain id?
There is a specific library that was created to handle this kind of scenarios: https://github.com/ReactTraining/react-router/issues/394#issuecomment-220221604
Install the react-router-hash-link library npm install --save react-router-hash-link
Our navigation component now looks like this (new items appear in bold and highlighted in red):
import { BrowserRouter as Router, ****Switch**,** Route, Link } from 'react-router-dom';
import { HashLink } from 'react-router-hash-link';const NavMenu = () => (
<Router>
<ul>
<li>
<Link to="/">home</Link>
</li>
<li>
<Link to="/now">now</Link>
</li>
<li>
<HashLink to="/#myWork">work</HashLink>
</li>
<li>
<Link to="https://medium.com/@ali.haydar">blog</Link>
</li>
</ul>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/now" component={Now} />
</Switch>
</Router>
)
Link to an external page
The “blog” link in our page still does not work. How to navigate to an external website?
A possible solution is to add a route that redirects the the external website.
<Route path="/blog"
component={() => {
window.location.href = 'https://medium.com/@ali.haydar';
return null;
}}
but this would show a flicker or the non-existing /blog page, then a redirection would happen. If your Router does not include a header/footer, then these will flicker before having the new website open.
It could be possible to handle this through a simple component including a loading spinner (e.g.)
<Route
path="/blog"
component={Redirect}
loc="https://medium.com/@ali.haydar"
/>
Another alternative would be not to use a route at all, and update the <Link> to be as follows:
<Link to={{ pathname: 'https://medium.com/@ali.haydar' }}
target="_blank" >
blog
</Link>
This looks tidy, but in this case why don’t we keep using the html anchor <a>. I don’t see any problem in mixing the react router <Link> with <a>. I decided to go with this solution:
So the final code will look like this:
import { BrowserRouter as Router, ****Switch**,** Route, Link } from 'react-router-dom';
import { HashLink } from 'react-router-hash-link';const NavMenu = () => (
<Router>
<ul>
<li>
<Link to="/">home</Link>
</li>
<li>
<Link to="/now">now</Link>
</li>
<li>
<HashLink to="/#myWork">work</HashLink>
</li>
<li>
<a href="https://medium.com/@ali.haydar">blog</a>
</li>
</ul>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/now" component={Now} />
</Switch>
</Router>
)
The react router documentation is super nice and useful (https://reactrouter.com/web/guides/quick-start), I highly recommend checking it out when working with React router — there are much more advanced features there as well.
Any feedback or comments would be appreciated.