
A fundamental component of traditional applications and single page applications alike is Navigationâââbeing able to move from one page to another.
Okay, and so what?
Wait up!
In this article, Iâll not only show you the nuances of navigating within your React/Redux applications, Iâll show you how to do this declaratively! Youâll also learn how to maintain state across your appâs navigation switches.
Ready?
đ NB: In this article, I assume you have a decent understanding of how Redux works. If not, you may want to check out my article on Understanding Redux đ.
The application weâll be working with
In a bid to make this as pragmatic as possible, I have set up a simple application for this purpose.
The following GIF is that of EmojiLand.

EmojiLand is a simple app, but just good enough to help you digest the very important tips Iâll be sharing in this article.
Notice how the app stays on a current route, but when the button is clicked it performs some fake action and redirects to another route upon completion of the fake action.
In the real world, this fake action could be a network request to fetch some resource, or any other async action.
Just so weâre on the same page, let me share a bit on how the EmojiLand app is built.
A quick overview of how EmojiLand is built
To work along, grab the applicationâs repo from GitHub. If youâre feeling lazy, feel free to skip.
Clone the repo: git clone https://github.com/ohansemmanuel/nav-state-react-router.git
Move into the directory: cd nav-state-react-router
Install the dependencies: yarn install
or npm install
Then run the application: yarn start
or npm start
Done that?
The app is a basic react with redux setup. A very minimal set up with react-router is also included.
In containers/App.js
youâll find the 6 routes contained in this application.
Belowâs the full code representation:
const App = () => (
<Router>
<Switch>
<Route exact path="/" component={AngryDude} />
<Route path="/quiet" component={KeepQuiet} />
<Route path="/smile" component={SmileLady} />
<Route path="/think" component={ThinkHard} />
<Route path="/thumbs" component={ThumbsUp} />
<Route path="/excited" component={BeExcited} />
</Switch>
</Router>
);
Each route leads to an emoji component. /quiet
renders the KeepQuiet
component.
And hereâs what the KeepQuiet
component looks like:
import React from "react"; import EmojiLand from "../components/EmojiLand"; import keepQuietImg from "../Images/keepquiet.png"; import emojiLand from "./emojiLand";
const KeepQuiet = ({ appState, handleEmojiAction }) => ( <EmojiLand EmojiBg="linear-gradient(120deg, #a6c0fe 0%, #f68084 100%)" EmojiImg={keepQuietImg} EmojiBtnText="Keep Calm and Stay Quiet." HandleEmojiAction={handleEmojiAction} appState={appState} /> );
export default emojiLand(KeepQuiet);
It is simple functional component that renders an EmojiLand
component. The construct of the EmojiLand
component is in components/EmojiLand.js
.
It is isnât very much of a big deal, and you can have a look on GitHub.
Whatâs important is that it takes in some props such as a background gradient, image, and button text.
Whatâs more delicate is the exported component.
Please have a look at the last line of the code block above.
export default emojiLand(KeepQuiet);
emojiLand
right there is an higher order component. All it does is make sure that when you click a button in any of the emoji components, it simulates a fake action for about 1000ms. Remember that in practice this may be a network request.
The emojiLand
higher order component does this by passing the appState
props into the emoji components. In this example, KeepQuiet
When any of the emoji components is first rendered, appState
is an empty string, ""
. After about 1000ms, appState
is changed to DO_SOMETHING_OVER
Where DO_SOMETHING_OVER
is represented as a constant, just as shown below:
In constants/action-types
:
export const DO_SOMETHING_OVER = "DO_SOMETHING_OVER";
Now, this is how every emoji component in this app works!
Also remember that at each route, a separate EmojiLand component is rendered.






Redirecting like a React champ
Upon the completion of the fake process, letâs assume you wanted to redirect/move to another route within the EmojiLand application.
How do you do this?
Firstly, remember that on hitting the home route, whatâs rendered is theAngryDude
component.

The more declarative approach for handling redirects is to use the Redirect
component from React-router.
Let me show you how.
Since we want to redirect from the AngryDude
component, first, you import the Redirect
component within containers/AngryDude.js
like this:
import { Redirect } from "react-router-dom";
For redirects to work, it has to be rendered like a regular component. In our particular example, weâll be redirecting when the appState
holds the value, DO_SOMETHING_OVER
i.e the fake action has been completed.
Now, hereâs the code for that:
const AngryDude = ({ appState, handleEmojiAction }) => {
return appState === DO_SOMETHING_OVER ? (
<Redirect to="/thumbs" />
) : (
<EmojiLand
EmojiBg="linear-gradient(-180deg, #611A51 0%, #10096D 100%)"
EmojiImg={angryDudeImg}
EmojiBtnText="I'm so pissed. Click me"
HandleEmojiAction={this._handleEmojiAction}
appState={this.props.appState}
/>
Now, if appState
is equal to DO_SOMETHING_OVER
, the Redirect
component is rendered.
<Redirect to="/thumbs" />
Note that a required to
prop is added to the Redirect
component. This prop is required to know where to redirect to.
With that in place, hereâs that in action:

If we go ahead an do the same for the other route components, we can successfully redirect through all the routes!
Hereâs that in action:

That was easy, right?
Thereâs a bit of a problem though, and Iâll address that in the next section.
Avoiding redirects from replacing the current route in history
Iâll open up a new browser and click through the app, but at some point, Iâll attempt to go back i.e by using the back browser button.
Have a look below:

Note that when I click the back button, it doesnât go back to the previous route but takes me back to my browserâs homepage.
Why?
This is because by default, using the Redirect
component will replace the current location in the browserâs history stack.
So, even though we cycled multiple routes, the routes replaced each other in the browserâs ârecordsâ.
To the browser, we only visited one route. Thus, hitting the back button took me back to the homepage.
Itâs like having an Arrayâââbut instead of pushing to the array, you replace the current value in the array.
Thereâs a fix though.
The Redirect
component can take a push
prop that deactivates this behaviour. With the push
prop, each route is pushed unto the browserâs history stack and NOT replaced.
Hereâs how that looks in code:
return appState === DO_SOMETHING_OVER ? (
<Redirect push to="/thumbs" />
) : (
<EmojiLand
EmojiBg="linear-gradient(-180deg, #611A51 0%, #10096D 100%)"
EmojiImg={angryDudeImg}
EmojiBtnText="I'm so pissed. Click me"
HandleEmojiAction={handleEmojiAction}
appState={appState}
/>
);
And hereâs the result of that.

Note how we can now navigate back to previously visited routes!
Maintaining navigation state
As you move from one route to another, variables in the previous route arenât carried over to the next route. They are gone!
Yes gone, except you do some work on your end.
Whatâs interesting is that the Redirect
component makes this quite easy.
As opposed to passing a string to
prop into Redirect
, you could also pass in an object.

Whatâs interesting is that with the object representation, you can also pass in a state
object.
Within the state
object you may now store any key value pairs you wish to carry over to the route being redirected to.

Letâs see an example in code.
When redirecting from the AngryDude
component to ThumbsUp
, letâs pass in some values into the state field.
Hereâs what we had before:
<Redirect push to="/thumbs" />
Thatâs to be changed to this:
<Redirect
push
to={{
pathname: "/thumbs",
state: {
humanType: "Cat Person",
age: 12,
sex: "none"
}
}}
/>
Now, I have passed in 3 different key value pairs! humanType
, age
, and sex
But upon redirecting to the /thumbs
route, how do I receive these values?
For route components, react-router makes available a certain location
prop. Within this location
prop, you may access the state object like this, location.state
or this.props.location.state
NB: Route components are components rendered by the react-routerâs <Route> component . They are usually in the signature, <Route component= {Component} />
Hereâs an example of me logging the state object received in the new route, /thumbs
i.e within the newly rendered Thumbs
component
const ThumbsUp = ({ appState, handleEmojiAction, location }) => {
console.log(location.state);
return appState === DO_SOMETHING_OVER ? (
<Redirect push to="/quiet" />
) : (
<EmojiLand
EmojiBg="linear-gradient(-225deg, #DFFFCD 0%, #90F9C4 48%, #39F3BB 100%)"
EmojiImg={thumbsUpImg}
EmojiBtnText="You rock. Thumbs up!"
HandleEmojiAction={handleEmojiAction}
appState={appState}
/>
);
};
Note how the location prop is deconstructed and then thereâs the console.log(location.state)
After been redirected, and the dev console inspected, the state object is indeed right there!

You may even go a little further and actually render some UI component based on the passed in state.
Hereâs what I did:

By grabbing the state passed into ThumbsUp
, I mapped over it and rendered the values below the button. If you care about how I did that, have a look at the source code in components/EmojiLand.js
Now weâve made some decent progress!
Any real world value?
You may have wondered all the while, âyes this is cool, but where do I use it in the real world?â
There are many use cases, but one very common one is where you have a list of results rendered in a table.
However, each row in this table is clickable, and upon clicking a row, you want to display even more information about the clicked values.
You could use the concepts here to redirect to the new route and also pass in some values from the table row to the new route! All by utilising the redirectâs state object within the to
prop!
But, thereâs another solution!
In the dev world, there are usually multiple ways to solve a problem. I want this article to be as pragmatic as possible, so Iâll show you the other possible way to navigate between routes.
Assume that we wanted to be redirected to from the /thumbs
route to the quiet
route after performing some action. In this case, we want to do this without using the Redirect
component.
How would you go about this?
Unlike the previous solution where we rendered the Redirect
component, you could use the slightly more imperative method shown below:
history.push("/quiet)
or this.props.history.push("/quiet")
Okay, but where does this history
object come from?
Just like location
in the previous example, react-router also passes down a history
prop into route components.
Hereâs what we had in containers/Thumbs.js
 :
const ThumbsUp = ({ appState, handleEmojiAction, location }) => {
return appState === DO_SOMETHING_OVER ? (
<Redirect push to="/quiet" />
) : (
<EmojiLand
EmojiBg="linear-gradient(-225deg, #DFFFCD 0%, #90F9C4 48%, #39F3BB 100%)"
EmojiImg={thumbsUpImg}
EmojiBtnText="You rock. Thumbs up!"
HandleEmojiAction={handleEmojiAction}
appState={appState}
locationState={location.state}
/>
);
};
Now, we may use the history
object like this:
const ThumbsUp = ({ appState, handleEmojiAction, location, history }) => {
if (appState === DO_SOMETHING_OVER) {
history.push("/quiet");
}
return (
<EmojiLand
EmojiBg="linear-gradient(-225deg, #DFFFCD 0%, #90F9C4 48%, #39F3BB 100%)"
EmojiImg={thumbsUpImg}
EmojiBtnText="You rock. Thumbs up!"
HandleEmojiAction={handleEmojiAction}
appState={appState}
locationState={location.state}
/>
);
};
And now, the results are just the same:

Just as expected, we still had the redirection possible!
It is important to note that you can also pass in some state values like this:
history.push("/quiet", {
hello: "state value"
})
Simply pass in a second object parameter into the history.push
function.
Weâve got all this out of the box
Do you realise that we havenât had to do any âextraâ work on the Redux side of things?
All we had to do was learn the APIs react-router
makes available. This is good, and it explains the fact that react-router
and redux
work just fine out of the box.
This app uses redux
, but thatâs not a problem.
Got that?
Something is (or perhaps, may be) wrong with our approach
Actually, nothing is wrong with the approaches weâve discussed so far. They work just fine!
However, there are a few caveats, and depending on how you love to work, or the project youâre working on, you may not find them bearable.
Mind you, I have worked with the previous patterns on large scale project and they work just fine.
However, a lot of Redux purists would prefer to be able to navigate routes by dispatching actions. Since thatâs the primary way of provoking a state change.
Also, many also prefer to synchronise the routing data with the Redux store i.e to have the route data saved within the Redux store.
Lastly, they also crave being able to enjoy support for time travel debugging in their Redux devtools as you navigate various routes.
Now, all of this isnât possible without some sort of deeper integration between react-router and redux.
So, how can this be done?
Considering a deeper integration between React-Router and Redux
In the past, react-router offered the library, react-router-redux for this purpose. However, at the time of writing, the project has been deprecated and is no longer maintained.

I guess it can be still be used, but you may have some fears using a deprecated library in production.
Thereâs still good news as the react-router-redux maintainers advice you use the library, connected-react-router
It does have a bit of setup to use, but it isnât a lot if you need the benefits it gives.
Letâs see how that works, and what we may learn from integrating that into our project, Emojiland.
Implementing Redux in your app? Track Redux state and actions with LogRocket
Debugging React applications can be difficult, especially when there is complex state. If youâre interested in monitoring and tracking Redux state for all of your users in production, try LogRocket. https://logrocket.com/signup/
LogRocket is like a DVR for web apps, recording literally everything that happens on your site. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.
The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.
Modernize how you debug your React apps – Start monitoring for free.
Integrating Connected-React-Router into EmojiLand
The first set of things to do are with the Redux store.
1. Create a history object
Technically, thereâs a DOM history object for manipulating the browserâs history session.
Letâs programmatically create one ourselves.
To do this, import createBrowserHistory
from history
In store/index.js
:
...
import { createBrowserHistory } from 'history'
...
history
is a dependency of the react-router-dom
package, and Itâs likely already installed when you use react-router in your app.
After importing createBrowserHistory
, create the history object like this:
..
const history = createBrowserHistory()
Still in the store/index.js
file.
Before now, the store
was created very simply, like this:
const store = createStore(reducer);
Where the reducer
refers to a reducer function in reducers/index.js
, but that wonât be the case very soon.
2. Wrap the root reducer
Import the following helper function from the connected-react-router
library
import { connectRouter } from 'connected-react-router'
The root reducer must now be wrapped as shown below:
const store = createStore(connectRouter(history)(reducer));
Now the reducer will keep track of the router state. Donât worry, youâll see what that means in a bit.
In order to see the effect of weâve done has so far, in index.js
I have exported the redux store globally, like this:
window.store = store;
Now, within the browser console, you can check whatâs in the redux state object with store.getState()
Hereâs that in action:

As you can see, thereâs now a router
field in the redux store! This router
field will always hold information about the current route via a location object e.g pathname
, state
etc.
We arenât done yet.
In order to dispatch route actions, we need to apply a custom middleware from the connected-react-router
library.
Thatâs explained next
3. Including a custom middleware
To include the custom middleware for handling dispatched actions, import the needed routerMiddleware
middleware from the library:
...
import { connectRouter, routerMiddleware } from 'connected-react-router'
Then use the applyMiddleware
function from redux:
...
import { createStore, applyMiddleware } from "redux";
...
const store = createStore(
connectRouter(history)(reducer),
applyMiddleware(routerMiddleware(history))
);
Now, weâre almost done. Just one more step.
4. Use the Connected Router!
Remember that react-redux gives us a Route
component. However, we need to wrap these Route
components in a ConnectedRouter
component from the connected-react-router
library.
Hereâs how:
First, in index.js
you import the ConnectedRouter
component.
import { ConnectedRouter } from 'connected-react-router'
...
Hereâs the render function of the index.js
file:
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
Remember that App
renders the different routes in the app.
const App = () => (
<Router>
<Switch>
<Route exact path="/" component={AngryDude} />
<Route path="/quiet" component={KeepQuiet} />
<Route path="/smile" component={SmileLady} />
<Route path="/think" component={ThinkHard} />
<Route path="/thumbs" component={ThumbsUp} />
<Route path="/excited" component={BeExcited} />
</Switch>
</Router>
);
Now, in index.js
 , wrap the App
component with the ConnectedRouter
component. The ConnectedRouter
component should be placed second only to the Provider
component from react-router
Hereâs what I mean:
render(
<Provider store={store}>
<ConnectedRouter>
<App />
</ConnectedRouter>
</Provider>,
document.getElementById("root")
);
One more thing!
Right now, the app wonât work as expected because the ConnectedRouter
requires a history
prop i.e the history object we created earlier.

Since we need the same object in more than one place, we need it as an exported module.
A quick fix is to create a new file store/history.js
import { createBrowserHistory } from "history";
const history = createBrowserHistory();
export default history;
Now, this exported history
object will be used in the both places where it is needed.
In index.js
it is imported like this:
import history from "./store/history";
And then passed into the ConnectedRouter
component as shown below:
render(
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>,
document.getElementById("root")
);
With this, the setup is done, and the app worksâââwithout the pesky errors we saw earlier!
Keep in mind I have only set up the connected-react-router
but I encourage you check out the more advanced usage of this library.
Thereâs more you can do with the connected-react-router
library and most of those are documented in the official FAQs. Also, if you have a more robust set up with the Redux devtools, and a logger middleware, be sure to take advantage of time travel and the action logger!
Conclusion
I hope this has been as much fun as it was for me!
If youâve got any questions, be sure to drop them in the comment section and Iâll be happy to help.
Go build something awesome, and Iâll catch you later!