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 Authentication

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:

class AuthorizedRoute extends Component<stateprops &="" dispatchprops="" routeprops,="" {}=""> {</stateprops>

 public componentWillMount() {
   if (!this.props.token) {
     this.props.dispatchLogin();
   }
 }

 public render() {
   const { component: RouteComponent, render, token, ...rest } = this.props;
   if (!this.props.token) {
     return <route {="" ...rest="" }="" render="{" this.renderfalse="">;</route>
   }
   if (render) {
     return <route {="" ...rest="" }="" render="{">;</route>
   }
   return <route {="" ...rest="" }="" component="{" routecomponent="">;</route>
 }
 private renderFalse = () => false;
}

interface StateProps {
 token?: string;
}

const mapStateToProps = (state: AppState): StateProps => ({
 token: tokenSelector(state),
});

interface DispatchProps {
 dispatchLogin: () => LoginPromise;
 push: (location: LocationDescriptor, state?: LocationState) => void;
}

const mapDispatchToProps = (dispatch: Dispatch) => ({
 dispatchLogin: () => dispatch(login()),
});

export default connect<stateprops, dispatchprops,="" routeprops="">(mapStateToProps, mapDispatchToProps)(AuthorizedRoute);</stateprops,>

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 the Onedot App, 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></layout>
  <switch></switch>
    <route exact="" path="/files" component="{" datafiles="" }=""></route>
    <route exact="" path="/files/:fileId" component="{" datafile="" }=""></route>
  

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="" }=""></route>

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></layout>
  <switch></switch>
    <route exact="" path="/files" component="{" datafiles="" }=""></route>
    <route exact="" path="/files/:fileId" component="{" datafile="" }=""></route>
  

One solution could have been 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 documentation.

So, instead of adding the Layout as a parent component of Switch. We went for this solution:

const renderWithLayout = (Component) => (props) => (
  <layout {="" ...props="" }=""></layout>
  <component {="" ...props="" }=""></component>
  
);

This renderWithLayout will be used in our Routes:

<switch></switch>

   exact
   path="/files"
   render={ renderWithLayout(DataFiles) }
 />

   exact
   path="/files/:fileId"
   render={ renderWithLayout(DataFile) }
 />

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 the React Router Docs: The render prop receives all the same route props as the component render prop.

const renderWithLayout = (Component) => (props) => (
  <layout {="" ...props="" }=""></layout>
  <component {="" ...props="" }=""></component>
  
);

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.

For more on Redux patterns, you can read our How we connect Redux to our Services API blog post. Thanks for reading and be sure you check out our open engineering positions!