A couple of tricks with Redux and React-Router
Disclaimer: This article assumes a working knowledge of React, Redux and React-Router. One example is in Typescript because I wanted to share production code.
We would like to share some of the tricks we use that have allowed us to develop faster and simpler.
Route with Auth
As any other web application. We have some routes that need the user to be logged in. We use our own custom Route
for those. We called our route component AuthorizedRoute
.
The usage of AuthorizedRoute
is the same as if it was a React-Router Route
Component:
<AuthorizedRoute exact path="/files" component={ DataFiles } />
As you can see, AuthorizedRoute
uses the Route
Component in its render. However, we decide what we render in the Route
Component. In this specific case, we care whether the user is logged in or not. We know whether the user is logged in by checking the token in Redux State.
Not just that, if the user is not logged in, we also dispatch an action that will trigger the login. In our specific case, the user will be redirected to the login page.
The combination of React Router and Redux, allows us to easily add routes that need authentication with no extra code.
Page Layout
In Onedot’s web application, most of the pages share a common Layout. They have the same sidebar, header, positioning in the center of the page, etc.
Hence, all the Routes that share the same Layout are wrapped with a Layout component. With React Router you can do this for those situations:
<Layout>
<Switch>
<Route exact path="/files" component={ DataFiles } />
<Route exact path="/files/:fileId" component={ DataFile }/>
</Switch>
</Layout>
That’s cool. All the pages have the same layout now.
We went slightly further and in our layout we also have Notifications and Overlays. Notifications are the common messages after something has been accomplished or failed: Your file cool_data.csv has been removed. Overlays are modals that expect the user to perform a specific action. For example when creating a folder and the user needs to enter the name.
The Notifications and Overlays are triggered by Redux Actions. Hence, they are Containers that are connected to Redux. We have a part of the state that manages active notifications and overlays.
This is possible because the Provider
from React Redux is a parent of the previous code.
However, we realized that in Overlays we needed the information of the parameter of the url. The parameter you set when you create the route:
<Route exact path="/files/:fileId" component={ DataFile } />
Some of our Overlays needed to know the value of fileId
parameter.
Our first approach was to add it to the part of the state in Redux that was triggering the Overlay. That was annoying. Whatever was triggering the Overlay had to have that data. Either because it was connected directly to React Router with withRouter
or because the parent was passing the data as a prop.
We then tried connecting the Overlay to React Router directly with withRouter
. That connected the component to the Router, but we didn’t have information about the path. The parameter fileId
was not there. Only the children of the component in Route
have access to information about the specific route. Remember that Overlay was in the Layout which was outside of the Route
.
<Layout>
<Switch>
<Route exact path="/files" component={ DataFiles } />
<Route exact path="/files/:fileId" component={ DataFile }/>
</Switch>
</Layout>
One solution could have bee to add the Overlays Component to each route. It would have meant that every time we create a new route that uses Overlays (which are most of them) we would have to remember to add it. That was not a good solution.
The solution we found was to use the render
method in the Route
component. Instead of passing the component
prop, you can pass a render
prop to Route
. Check out the render prop in their Docs.
So, instead of adding the Layout as a parent component of Switch. We went for this solution:
const renderWithLayout = (Component) => (props) => (
<Layout { ...props }>
<Component { ...props } />
</Layout>
);
This renderWithLayout
will be used in our Routes:
<Switch>
<Route
exact
path="/files"
render={ renderWithLayout(DataFiles) }
/>
<Route
exact
path="/files/:fileId"
render={ renderWithLayout(DataFile) }
/>
</Switch>
Worth mentioning that now Layout
is not wrapping Switch
but is wrapping the Component of each Route.
When we call renderWithLayout(DataFiles)
we get back a function that will be called by React Router. The parameter passed will be all the same props that are passed to the Component in the prop component
.
From React Router Docs:
The render prop receives all the same route props as the component render prop.
const renderWithLayout = (Component) => (props) => (
<Layout { ...props }>
<Component { ...props } />
</Layout>
);
This means that our Layout
component always receives all the information of the route props. Which means that we finally can connect all its child components (Overlays) to React Router and have all the data from the path. Specifically the parameter of the route.
Conclusion
React Router and Redux are by itself powerful tools. However, when you combine them together you get something even more powerful than the sum of its part.
These are just 2 of the combinations we use. What pattern do you use? We’d love to hear your thoughts on this and other patterns. Don’t hesitate to leave a comment.
For more on Redux patterns, you can read our How we connect Redux to our Services API article here on medium.
Thanks for reading!
Originally posted on Onedot Blog