Components are not receiving route props

Viewed 561

The docs state

All three render methods will be passed the same three route props match location history

And they showcase it with this example

import ReactDOM from "react-dom";
import { BrowserRouter as Router, Route } from "react-router-dom";

// All route props (match, location and history) are available to User
function User(props) {
  return <h1>Hello {props.match.params.username}!</h1>;
}

ReactDOM.render(
  <Router>
    <Route path="/user/:username" component={User} />
  </Router>,
  node
);

Seems easy enough.

But then I try it with my code and that's not working.

import * as React from 'react';
import { Switch, Route, BrowserRouter, Redirect } from 'react-router-dom';
import { isFalsy, readFromStorage } from 'utils/general.utils';

import { Homepage } from './pages/homepage/Loadable';
import { Header } from './global/components';
import { Register } from './pages/auth/register/Loadable';
import { Login } from './pages/auth/login/Loadable';
import { ACCESS_TOKEN } from 'constants/storage.constants';

const PrivateRoute = ({ children, ...rest }) => {
  const accessToken = readFromStorage(ACCESS_TOKEN);
  return (
    <Route
      {...rest}
      render={({ location }) =>
        !isFalsy(accessToken) ? (
          children
        ) : (
          <Redirect
            to={{
              pathname: '/login',
              state: { from: location },
            }}
          />
        )
      }
    />
  );
};

export function App(props) {
  const routes = [
    {
      path: ['/'],
      component: Homepage,
      exact: true,
      private: false,
    },
    {
      path: ['/register'],
      component: Register,
      private: false,
    },
    {
      path: ['/login'],
      component: Login,
      private: false,
    },
  ];

  return (
    <BrowserRouter>
      <Header />
      <Switch>
        {routes.map(route => {
          const Component = route.component;
          const RouteWrapper = route.private ? PrivateRoute : Route;
          return (
            <RouteWrapper
              key={route.path[0]}
              path={route.path}
              exact={route.exact}
            >
              <Component {...props} />
            </RouteWrapper>
          );
        })}
      </Switch>
    </BrowserRouter>
  );
}

And yes I am wrapping my App in ReactDom likeso

ReactDOM.render(
  <Provider store={store}>
    <HelmetProvider>
      <React.StrictMode>
        <App />
      </React.StrictMode>
    </HelmetProvider>
  </Provider>,
  ROOT,
);

But the Register, Login and Home component don't have the props of the route. What am I doing wrong here?

I am using v5.2.0

1 Answers

Issue

You simply are not passing the route props on through to the rendered components since you are always rendering children components as wrapped children and not on one of render, component, or children props.

const PrivateRoute = ({ children, ...rest }) => {
  const accessToken = readFromStorage(ACCESS_TOKEN);
  return (
    <Route
      {...rest}
      render={({ location }) =>
        !isFalsy(accessToken) ? (
          children // <-- Just renders children, passes no props
        ) : (
          <Redirect
            to={{
              pathname: '/login',
              state: { from: location },
            }}
          />
        )
      }
    />
  );
};

And you pass along the props that were passed to App

export function App(props) { // <-- props
  const routes = [....];

  return (
    <BrowserRouter>
      <Header />
      <Switch>
        {routes.map(route => {
          const Component = route.component;
          const RouteWrapper = route.private ? PrivateRoute : Route;
          return (
            <RouteWrapper
              key={route.path[0]}
              path={route.path}
              exact={route.exact}
            >
              <Component {...props} /> // <-- spread here, no route props
            </RouteWrapper>
          );
        })}
      </Switch>
    </BrowserRouter>
  );
}

Solution

  1. Rewrite your PrivateRoute component to render either a Route or Redirect so the route props can be passed along.

    const PrivateRoute = (props) => {
      const accessToken = readFromStorage(ACCESS_TOKEN);
    
      return !isFalsy(accessToken) ? (
        <Route {...props} />
      ) : (
        <Redirect
          to={{
            pathname: '/login',
            state: { from: props.location },
          }}
        />
      );
    };
    
  2. Pass (nearly) the entire route config object to the wrapper. Here you will be spreading path, exact, component (or render, etc) into either a Route or PrivateRoute which will pass on the route props.

    <Switch>
      {routes.map(({ private: isPrivate, ...props }) => {
        const RouteWrapper = isPrivate ? PrivateRoute : Route;
        return (
          <RouteWrapper
            key={props.path[0]}
            {...props}
          />
        );
      })}
    </Switch>
    

Demo

Edit components-are-not-receiving-route-props

enter image description here

Related