user3787120
user3787120

Reputation: 47

react-router-dom conditionally build routes and pass parameters to class based component

I am building an app with react-router-dom that initializes by making AJAX requests and then uses routes to pass parameters to class-based sub components. I can't figure out how to simultaneously 1) render <Routes> conditionally and 2) pass parameters to sub components.

I have a parent level <RouterApp /> component which is functional, and a conditionally rendered component <MyClassBasedComponent>.

In my initial attempt, I set up my code like this:

function RouterApp() {
  const preloadData = usePreloadData(); /*custom hook to make an AJAX call*/
  
  /*Conditionally show loader screen or app content*/
  if (!preloadData) {
    return <WelcomeLoader />;
  } else {
    //build routes obj
    const routesObj = {
      "/": () => (props) => <About />,
      "/component/:clientid": (clientID) => <MyClassBasedComponent 
                                             essentialData={preloadData}
                                             clientID={clientID}/>,
    };


    return (
      <Router>
        <HeaderMenu />
          <div className="app-body router-app">
             {useRoutes(routesObj)}
          </div>
      </Router>
    );
    
  }
}

This throws a compile error: React Hook "useRoutes" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?

My next attempt was to create a routes object in array-of-dictionaries form:

function RouterApp() {
  const preloadData = usePreloadData(); /*custom hook to make an AJAX call*/
  
  /*Conditionally show loader screen or app content*/
  if (!preloadData) {
    return <WelcomeLoader />;
  } else {
    //build routes obj
    
    const routesObj = [
      { path: "/", element: <About /> },
      { path: "/component/:clientid", "element": (clientid) => (<MyClassBasedComponent
                                             essentialData={preloadData}
                                             clientID={clientid}/>) },
      //Various other attempts below
      //{ path: "/component", element: <ClassComponentWithLink/> },
      //{ path: "/component/:clientid", element: (clientid)=><ClassComponentWithLink id={clientid}/> },
      //{ path: "/component/:clientid", element: <ClassComponentWithLink myProp={clientid}/> },
      
    ];
    const MyRoutes = (props) => useRoutes(props.routesObj);


    return (
      <Router>
        <HeaderMenu />
          <div className="app-body router-app">
             <MyRoutes 
                  routesObj = {routesObj}
              />
          </div>
      </Router>
    );
    
  }
}

When I use this form and pass a function to "element", <MyClassBasedComponent /> does not render.

When I use the form { path: "/component/:clientid", element: <MyClassBasedComponent /> },, the component renders, but when I interrogate its props, I don't see the value of clientid anywhere.

How can I accomplish this properly?

Here is the relevant documentation.

Upvotes: 1

Views: 974

Answers (1)

Drew Reese
Drew Reese

Reputation: 203466

Both implementations are incorrect. In react-router-dom@6 there are no longer any route props (history, location, and match) and were replaced by React hooks. Since you are also using a React class component, the hooks can't be used, directly, so you'll need to either convert the class components into function components, or create a wrapper component or custom withRouter HOC to access the hooks and pass along/inject the old route props. I won't cover converting a class component into a function component.

  • Wrapper example

    Create a wrapper component that uses the useParams hook to read the clientid route param and render the MyClassBasedComponent component.

      const MyClassBasedComponentWrapper = () => {
        const { clientId } = useParams();
        return (
          <MyClassBasedComponent
            essentialData={preloadData}
            clientID={clientid}
          />
        );
      };
    

    Then specify the routes config as per expected.

    const routesObj = [
      { path: "/", element: <About /> },
      {
        path: "/component/:clientid",
        element: <MyClassBasedComponentWrapper />
      },  
    ];
    
  • Custom withRouter example

    const withRouter = Component => props => {
      const params = useParams();
      ... use the other hooks here if you need them ...
      return <Component {...props} params={params} />;
    };
    

    Decorate the MyClassBasedComponent component so it receives params as a prop.

     export default withRouter(MyClassBasedComponent);
    

    Then specify the routes config as per expected.

    const routesObj = [
      { path: "/", element: <About /> },
      {
        path: "/component/:clientid",
        element: <MyClassBasedComponent />
      },  
    ];
    

    Inside the MyClassBasedComponent component access the clientId param from this.props.params.

     `const { clientId } = this.props.params;
    

Configuring the RouterApp component:

const routesObj = [
  { path: "/", element: <About /> },
  {
    path: "/component/:clientid",
    element: <MyClassBasedComponent />
  },  
];

function RouterApp() {
  const preloadData = usePreloadData();
  const routes = useRoutes(routesObj);
  
  return !preloadData
    ? <WelcomeLoader />
    : (
      <Router>
        <HeaderMenu />
        <div className="app-body router-app">
          {routes}
        </div>
      </Router>
    );
  };
}

Upvotes: 1

Related Questions