WithRouter-Advanced Features of React Router for Single Page Apps

Written by gregfilipczak | Published 2017/09/18
Tech Story Tags: react | react-router | single-page-applications | javascript

TLDRvia the TL;DR App

Last week, I outlined a basic setup for React Router to set up a single page application. I used a portfolio site as an example, and there wasn’t much complexity to the components being used. However, you’ll often have the need for deeply nested components, so React Router includes the withRouter method to give you access to the match, location, and history properties of the browser’s native API necessary to make your Link and Route components work correctly. It’s fairly easy to use, but there are a few issues to watch out for when using it. Here’s a contrived, basic example:

const App = () =><Router><IntermediaryComponentWithNoRoutes></Router>

The child of the Router won’t render the Routes components, so we’ll need to make use of withRouter() to retain access to the necessary props.

// We'll assume there's some important logic being omitted here for the sake of the exampleconst IntermediaryComponentWithNoRoutes = () =><div><NestedComponentThatRendersRoutes/></div>

To get access again, we can use withRouter():

const NestedComponentThatRendersRoutes = () =><div><Route exact path="/" component={Home} /><Route path="/about" component={About} /></div>

// In our exporting of the component, we wrap it in withRouter(), giving it access to the props that we need for the Routes

export default withRouter(NestedComponentThatRendersRoutes)

This is a nonsense example, but you should be able to see how it gets useful when you have many nested parts to your application that don’t need access to the parts of the React Router library.

For a more concrete example, I’m currently working on an app that uses Redux and nests heavily, so I’ll be borrowing some code from that with some minor changes. If you’re unfamiliar with routing with React Router, I’d advice taking a quick read of my previous blog and then referencing the docs. If you’re unfamiliar with Redux, there may be some unfamiliar code, but I’ll explain the basic flow.

I’ve omitted some, but here is the App component with the Router wrapping the main DashboardContainer component.

const App = ({ state }) =><Router><DashboardContainer {...state} /></Router>

export default App;

Nothing extraordinary about the code above, but you’ll see that we’re wrapping the DashboardContainer in the Router above. The code that actually renders the Routes is actually the Dashboard component and not the DashboardContainer. This is done to get access to parts of the redux store and action dispatching functions. If you’re unfamiliar with Redux and have questions or if you have suggestions about improvements, please let me know.

import React, { Component } from "react";import { connect } from "react-redux";import { logOut } from "../actions";import Dashboard from "../components/Dashboard";import { withRouter } from "react-router-dom";

class DashboardContainer extends Component {

componentDidMount() {const token = localStorage.getItem("token");if (!this.props.user && localStorage.getItem("token")) {this.props.getAuthenticatedUser(token);}}

render() {return (<div><Dashboard {...this.props} />}</div>);}}

const mapStateToProps = state => {return {user: state.user};};

const mapDispatchToProps = dispatch => {return {logOut: () => dispatch(logOut()),getAuthenticatedUser: token => dispatch(getAuthenticatedUser(token))};};

export default withRouter(connect(mapStateToProps, mapDispatchToProps)(DashboardContainer));

There’s a fair bit going on with the component above, including dispatching an asynchronous action to fetch user data. We won’t worry about those details for now, but the current implementation of the code requires that the intermediary code makes a conditional check for a user and pings the server if necessary. It does a good job of highlighting one use case for withRouter(). Because I won’t be rendering the Routes components until the Dashboard, we add the withRouter() to the function that we’re exporting. Here is that line again:

export default withRouter(connect(mapStateToProps, mapDispatchToProps)(DashboardContainer));

Again, ignore the connect, mapStateToProps, and mapDispatchToProps if you’re unfamiliar with Redux, but focus on how withRouter takes the DashboardContainer as an argument. This gives that component the necessary match, location, and history props that we’ll need down the line to make use of Routes, Links, and Redirects which will be used later. Also, take note that if you’re using connect from the react-router library, withRouter will need to wrap the container component that’s being connected and not the functional component that may actually render the Routes.

import { Route } from "react-router-dom";import React from "react";import BoardsContainer from "../containers/BoardsContainer";import BoardDashboard from "./BoardDashboard";

// Take note of how the render prop is being used on the Route. I'm //making it an anonymous function so that it has access to the props //will be needed.const Dashboard = ({ user, boards }) => {return (<div><Routeexactpath="/"render={() => <BoardsContainer boards={boards} user={user} />}/><Routepath="/boards/:board_id"render={() => <BoardDashboard boards={boards} />}/></div>);};

export default Dashboard;

I’ve left out some code, but there are a few things I’d like to highlight about the code above. Here are our Routes finally and we can use them because of the work that led up to this point. Something interesting to note here is that in order to pass the props down when using a route, you can use an anonymous function using the render property of a Route to add in the props that you need. For this code here, it’s rendering the user and boards that are stored in the Redux store.

Hopefully, now you’ll see how the utility of withRouter() even in smaller applications. As always, if you have any questions or suggestions, please let me know!


Published by HackerNoon on 2017/09/18