I am trying to understand the nuances behind a specific warning I see when working with React Router. I was trying to setup conditional routing based on whether or not the User was logged in or not. My code is as follows:
// AppRoutes.js
export const AppRoutes = ({ machine }) => {
const [state] = useMachine(machine);
let routes;
if (state.matches('authenticated')) {
routes = (
<React.Fragment>
<Route exact path="/"><HomePage /></Route>
<Route path="/contacts"><ContactsList /></Route>
</React.Fragment>
);
} else if (state.matches('unauthenticated')) {
routes = (
<Route path="/">
<LoginPage service={state.children.loginMachine} />
</Route>
);
} else {
routes = null;
}
return (
<BrowserRouter>
<Switch>{routes}</Switch>
</BrowserRouter>
);
};
Internally, the HomePage component redirects to /contacts
// HomePage.js
export const HomePage = () => {
return <Redirect to="/contacts" />;
};
Now with this code, the application works as I need it to, but I get a warning logged in the console:
Warning: <Route> elements should not change from controlled to uncontrolled (or vice versa). You provided a "location" prop initially but omitted it on a subsequent render.
I did some research and the only thing I could find was this https://stackoverflow.com/a/52540643 which seems to indicate that conditionally rendering the routes is causing the issue. However, conditional rendering of routes is the whole point -- I don't want unauthenticated users accessing /contacts
Then after some playing around, I modified the source as below:
// AppRoutes.js
export const AppRoutes = ({ machine }) => {
const [state] = useMachine(machine);
let routes;
if (state.matches('authenticated')) {
routes = (
<React.Fragment>
<Route path="/home">
<HomePage />
</Route>
<Route path="/contacts">
<ContactsList />
</Route>
<Redirect to="/home" />
</React.Fragment>
);
} else if (state.matches('unauthenticated')) {
routes = (
<React.Fragment>
<Route path="/login">
<LoginPage service={state.children.loginMachine} />
</Route>
<Redirect to="/login" />
</React.Fragment>
);
}
return (
<BrowserRouter>
<Switch>{routes}</Switch>
</BrowserRouter>
);
};
// HomePage.js
export const HomePage = () => {
return <Redirect to="/contacts" />;
};
Now this code redirects authenticated users to /contacts
and unauthenticated users to /login
, and doesn't log any warnings.
Everything works great, except I still don't understand why the warning no longer appears and how is this different from what I was doing earlier. As far as I can see and understand, I am doing conditional rendering of routes in both versions of the code. Why does one log a warning, while the other doesn't?
Any guidance??
Thanks!
The Route component from react-router is public by default but we can build upon it to make it restricted. We can add a restricted prop with a default value of false and use the condition if the user is authenticated and the route is restricted, then we redirect the user back to the Dashboard component.
The <Switch /> component will only render the first route that matches/includes the path. Once it finds the first route that matches the path, it will not look for any other matches.
The exact prop is used to define if there is an exactly the requested path.
According to docs: https://reactrouter.com/web/api/Switch/children-node
All children of a
<Switch>
should be<Route>
or<Redirect>
elements.
This means wrapping routes in <React.Fragment></React.Fragment>
or simply <></>
won't cut it.
Warning is present because these are not <Route>
nor <Redirect>
elements.
Solution 1: Wrapping in <Route>
then another <Switch>
I don't like this solution because you have to repeat fallback Routes
<Switch>
<Route exact path='/' component={Main} />
{some_condition && (
<Route path='/common'>
<Switch>
<Route exact path='/common/1' component={Secondary} />
<Route exact path='/common/2' component={Ternary} />
<Route component={NotFound} /> {/* this needs to be repeated here */}
</Switch>
</Route>
)}
<Route component={NotFound} />
</Switch>
Solution 2: Rendering routes as array
In the end this is what I went for. You have to include keys what is annoying but no extra elements needed.
<Switch>
<Route exact path='/' component={Main} />
{some_condition && [
<Route exact path='/common/1' key='/common/1' component={Secondary} />,
<Route exact path='/common/2' key='/common/2' component={Ternary} />
]}
<Route component={NotFound} />
</Switch>
You can also format it like this:
let routes;
if (some_condition) {
routes = [
<Route exact path='/common/1' key='/common/1' component={Secondary} />,
<Route exact path='/common/2' key='/common/2' component={Ternary} />
];
}
return (
<Switch>
<Route exact path='/' component={Main} />
{routes}
<Route component={NotFound} />
</Switch>
);
I just had the same issue, which Emanuel's answer did not fix for me. What worked is simply using <Switch>
instead of <>
(or <React.Fragment>
) inside another Route.
Correct me if I'm wrong, but I think having nested Routes/Switches with react-router-dom is fine.
Example:
<Switch>
<Route exact path='/' component={Main} />
{some_condition && (
<Route path='/common'>
<Switch>
<Route exact path='/common/path' component={Secondary} />
</Switch>
</Route>
)}
<Route path='/settings' component={Settings} />
</Switch>
However, this approach requires a common route path ("/common" in this example).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With