Reputation: 47
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
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