Hey Guys 👋, I'm going to show you how to create a custom router in react. We will use the browser's History API.
How to detect the current url
The url information is stored in window.location
. This returns a location object. The location.pathname
gives us the part of the url after the domain. For example, in a url - xdfjdkf.com/abc, abc
is stands for location.pathname
.
Building a simple Route component
We can render different components based on the location.pathname
. We take in 2 props - the route(pathname) and children(content we want to render)
export function Route({route, children}) {
return window.location.pathname === route ? children :
null
}
Building a Link component
I'm using the default html a
tag. Our link takes in 3 props - href(where the link should go to), className(for styling), children(what should the link say).
export function Link({href, className, children}) {
return (
<a href={href} className={className}>{children}</a>
)
}
You might be thinking, what is the difference between using our Link
component and an a
tag? Well, the difference is that when we click the a
tag, a page reload occurs which defeats the purpose of building a router for a SPA(Single Page Application).
To prevent this, let's setup an event listener to handle click events. We will call e.preventDefault()
to prevent the default action(page reload). Instead, we will use the window.history.pushState() method to change the URL.
The history.pushState() method adds an entry to the browser's session history stack.
export function Link({href, className, children}) {
const handleClick = (e) => {
e.preventDefault();
window.history.pushState({}, '', href);
}
return (
<a href={href} className={className} onclick={handleClick}>{children}</a>
)
}
This time around, when we try it, the URL changes but the rendered component does not change. This is because the rest of the application does not realise that the URL has changed. To do this we will dispatch a PopStateEvent.
A popstate event is dispatched to the window every time the active history entry changes between two history entries for the same document.
export function Link({href, className, children}) {
const handleClick = (e) => {
e.preventDefault();
window.history.pushState({}, '', href);
const event = new PopStateEvent('popstate');
window.dispatchEvent(event);
}
return (
<a href={href} className={className} onclick={handleClick}>{children}</a>
)
}
Now, we have to setup an event listener in the router component to listen to this event. I'm going to put this event listener in the useEffect
hook. In class based components, I'd add this method to componentDidMount
. We only want to wire this up 1 time, so I will specify an empty array for the dependencies. We will return a function from the useEffect
for cleanup i.e. removing the event listener.
export function Route({route, children}) {
useEffect(() => {
const onLocationChange = () => {
// Do something
}
window.addEventListener('popstate', onLocationChange);
return () => {
window.removeEventListener('popstate', onLocationChange);
}
}, [])
return window.location.pathname === route ? children :
null
}
When the pathname changes, we want all the route components to re-render. How do we do that? You guessed it! By using state.
const [currentPath, setCurrentPath] = useState(window.location.pathname);
The comparison to check if the url is correct can technically stay the same but I'm going to set it to currentPath
for simplicity's sake.
return currentPath === route ? children :
null
Some of you guys might be using CMD + click
or CTRL + click
to open links in new tabs. This is something a lot of tutorials miss out on. Let's implement this functionality in our Link
component.
export function Link({href, className, children}) {
const handleClick = (e) => {
if(e.metaKey || e.ctrlKey) {
return;
}
e.preventDefault();
window.history.pushState({}, '', href);
const event = new PopStateEvent('popstate');
window.dispatchEvent(event);
}
return (
<a href={href} className={className} onclick={handleClick}>{children}</a>
)
}
metaKey
stands for CMD
and ctrlKey
stands for CTRL
. These are basically boolean
values that tell us whether a user had pressed one of these keys while clicking the link. We want to return early and let the browser do its thing.
That's it for now. I hope you guys liked this post. If you have any questions, leave them in the comments and I'll try my best to answer them. Bye for now 👋.