Reputation: 431
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!
Upvotes: 10
Views: 5599
Reputation: 194
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>
);
Upvotes: 13
Reputation: 33
Try this...
None of the alternatives worked for me. So I used this and it worked perfectly:
First I set all my routes in an array:
const routes = [
<Route ...>,
<Route ...>,
<Route ...>
]
Then perform a map function:
{
condition === true
? routes.map(myRoute => myRoute)
: <Redirect to="/"/>
}
It works perfectly!!
Upvotes: -1
Reputation: 11
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).
Upvotes: 1
Reputation: 149
I was dealing with something similar. I had this:
<Switch>
{aCondition && (
<>
<Route ... />
<Route ... />
</>
)}
which brought me that same warning. Seeing your code I saw that you had a fragment too and in the "else" you haven't. So I tried with:
{aCondition ? (
<>
<Route ... />
<Route ... />
</>
) : (
<></>
)}
and it works just fine. It seems that having a conditional fragment confuses the switch.
Upvotes: 1