Technology moves fast.
Oftentimes, implementing new tech feels much like being a pioneer. Older, more stable tech usually has a wealth of resources around it and blog posts explaining weird quirks that the technology can exhibit. When implementing new things, however, you are the one hitting the quirks, putting in the hours to figure them out, and then documenting solutions to hopefully help those who come after you.
React Router v4 has just made the jump from Alpha to Beta, and we were eager to jump in and see what it has to offer. While implementing JWT for an application, we ran into one of the quirks React Router v4 has. Here, we hope to show how to implement protected client-side routes while avoiding some of the pitfalls we encountered.
Our approach mainly modeled a pattern set up by Josh Geller, which can be found here. The first step was setting up a login component. That component triggers an onLogin Redux function that dispatches various actions at certain points as the function executes. Upon successful completion of a login, a bearer token is returned from the API and stored both within Redux and sessionStorage. This all worked perfectly, and we were well on our way.
The next part of this implementation was protecting specific views that require a token. This was by far the most challenging portion. This is where we deviated from Josh’s approach. The problem has to do with including a Higher Order Component (HOC) within the Route component itself.
From Josh’s example in the routes/index.js, it is this line of code:
<span style="font-weight: 400"><Route path="protected" component={requireAuthentication(ProtectedView)}/></span>
When implementing this in React Router v4, you DO NOT want to follow this pattern of referencing a function call within a React Router route and passing a component into that HOC function. We were perplexed at first, as this seems to be a widely recommended approach for protecting views within routing.
Some of the symptoms we saw before finally diagnosing the issue included:
- Memory leak caused by infinitely dispatching Redux actions: Luckily, we had the Redux dev tools installed, as this did not cause a “Maximum call stack size exceeded” error; it simply froze up the tab and required us to force quit and restart.
- React render method recursively being called: We originally thought that the HOC was listening for props being updated. This would explain a loop like this: HOC creates component > component calls API and updates props > updating of props triggers HOC render to fire again > creates same component which calls API again > etc.
- Was checking auth in componentWillMount causing an infinite update because it’s constantly trying to see if the props for isAuthenticated are equal to something? No, but many posts out there do warn against using componentWillUpdate, which can cause recursive calls. We weren’t calling that lifecycle method, but be cautious of this when creating your own app.
We finally narrowed the issue down to the fact that we were calling the HOC function within the component prop for the Route component. So, how do you implement a protected route without passing your component through a HOC that checks for auth?
The docs for React Router v4 actually have a case where they implement auth via a PrivateRoute component. We implemented this and were still left with the memory leak. Upon further investigation, this approach essentially does the same thing we’ve tried before -- the PrivateRoute is simply a function that passes in a component, checks auth, and either renders the Route component or renders a Redirect component.
We eventually got a nudge in the right direction from Scott Luptowski's post on adding a login component to a React app. He offers an alternate approach, wherein a parent route checks for auth -- if the user is authenticated, the child routes are rendered; otherwise, the user is redirected back to the login component. You can find the code we modified under the “Alternate approach” section.
Do note that React Router v4 has made it a point to never allow you to use this.props.children
for rendering nested routes. So in Scott's EnsureLoggedInContainer
container, we swapped out this.props.children
for a list of the routes we wanted to render if someone is authenticated.
Voila! We have protected routes that check authentication without using a HOC, thus avoiding the infinite loop.
We hope this helps when you eventually have to implement protected routes with React and React Router v4. Additionally, we weren’t able to explain exactly why including an HOC in the component prop caused this infinite loop, so please leave a comment or tweet at us @Cuttlesoft if you know why this happens or have a more elegant way of implementing this feature.