React Router v4 - Switch doesn't work with route render - javascript

I'm using React Router to display the same component but with different props on each route:
<BrowserRouter>
<div>
<Route exact path="/" render={() => LOGGED_IN ? <Redirect to="/profile" /> : <Login />} />
<Route path="/profile/:id" render={(props) => <App {...props} page="profile" pageLadder={['Home', 'Profile']} />}/>
<Route path="/team" render={() => <App page="team" pageLadder={['Home', 'Team']} />}/>
<Route path="/transactions" render={() => <App page="transactions" pageLadder={['Home', 'Transactions']} />}/>
<Route path="/tournaments" render={() => <App page="tournaments" pageLadder={['Home', 'Tournaments']} />}/>
<Route path="/tournament/:id" render={(props) => <App {...props} page="tournament" pageLadder={['Home', 'Tournament', props.match.params.id]} />}/>
<Route path="/match/:id" render={(props) => <App {...props} page="match" pageLadder={['Home', 'Match', props.match.params.id]} />} />
<Route path="/scrims" render={() => <App page="scrims" pageLadder={['Home', 'Scrims']} />} />
<Route path="/faq" render={() => <App page="faq" pageLadder={['Home', 'FAQ']} />} />
<Route path="/staff" render={() => <App page="staff" pageLadder={['Home', 'Staff']} />} />
<Route path="/privacy" render={() => <App page="privacy" pageLadder={['Home', 'Privacy Policy']} />} />
<Route path="/tos" render={() => <App page="tos" pageLadder={['Home', 'Terms of Service']} />} />
</div>
</BrowserRouter>
I also need to catch 404 errors, so I added Switch and <Route component={NotFound} />:
<BrowserRouter>
<Switch>
<Route exact path="/" render={() => LOGGED_IN ? <Redirect to="/profile" /> : <Login />} />
... more routes
<Route component={NotFound} />
</Switch>
</BrowserRouter>
The 404 does work, but every <Link> element kind of stops working - after clicked, the url does change but the component stays the same, unless I refresh the site.
I tried changing my code to this to test:
<BrowserRouter>
<Switch>
<Route path="/team" component={Login} />
<Route path="/profile/" component={App} />
</Switch>
</BrowserRouter>
With this code the <Link> component does work as intended.
What can I do to make my first code work as intended?

From your code example:
class App extends Component {
constructor(props){
super(props);
// appStore is an instance of react-easy-state library
// App component puts the components together, the actual pages are in AppMain component (look below for code)
appStore.changePage(this.props.page, this.props.pageLadder, this.props.match ? this.props.match.params.id : -1);
// this function changes appStore.page.name value and it's a global type store
}
render() {
return (
<div>
<div>
<Sidebar />
<AppMain />
</div>
</div>
);
}
}
You are calling appStore.changePage in the constructor, which only get's called one time, when the component is first initialized. Even though you have the component on several routes, it's not getting unmounted when you change the route and props, meaning that the constructor is never getting called again.
What you need to do is use componentDidUpdate to change the page when your props change:
componentDidUpdate (prevProps, prevState) {
if(prevProps.page !== this.props.page) {
appStore.changePage(this.props.page, this.props.pageLadder, this.props.match ? this.props.match.params.id : -1);
}
}
Now, you compare the prevProps from the current props and if the page prop has changed, you need to trigger appStore.changePage again. Now, I'm not sure what appStore.changePage is doing so I'm not sure if you'll need to update <AppMain /> as well, but this should get you on the right track.

Related

Uncaught Error: [RouteWrapper] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>

I'm using React Router v6 and am creating private routes for my application.
In file Route.js, I've the code
export default function RouteWrapper({
element: Element,
isPrivate,
...rest
}) {
const { signed, loading } = useContext(AuthContext);
if (loading) {
return <div></div>;
}
if (!signed && isPrivate) {
return <Navigate to="/" />;
}
if (signed && !isPrivate) {
return <Navigate to="/dashboard" />;
}
return <Route {...rest} render={(props) => <Element {...props} />} />;
}
And in file index.js I've written as:
return (
<Routes>
<Route path="/" element={SignIn} />
<Route path="/register" element={SignUp} />
<Route path="/dashboard" element={Dashboard} isPrivate />
<Route path="/profile" element={Profile} isPrivate />
<Route path="/customers" element={Customers} isPrivate />
<Route path="/new" element={New} isPrivate />
<Route path="/new/:id" element={New} isPrivate />
</Routes>
);
}
Is there something I'm missing?
Issue
RouteWrapper isn't a Route component, and fails an invariant check by react-router-dom.
RouteWrapper is directly rendering a Route component, which if the first invariant wasn't failed would trigger another invariant violation. Route components can only be rendered directly by the Routes component or another Route component in the case of building nested routing.
In short, in react-router-dom#6 custom route components are no longer supported. You should instead use wrapper components/layout routes to handle this use case.
Solution
Convert RouteWrapper to a wrapper component that renders an Outlet component for nested routed components to be rendered into.
Example:
import { Navigate, Outlet } from 'react-router-dom';
export default function RouteWrapper({ isPrivate }) {
const { signed, loading } = useContext(AuthContext);
if (loading) {
return <div></div>;
}
if (!signed && isPrivate) {
return <Navigate to="/" />;
}
if (signed && !isPrivate) {
return <Navigate to="/dashboard" />;
}
return <Outlet />; // <-- nested routes render here
}
Wrap the routes you want to protect with the RouteWrapper.
return (
<Routes>
<Route element={<RouteWrapper />}>
<Route path="/" element={<SignIn />} />
<Route path="/register" element={<SignUp />} />
</Route>
<Route element={<RouteWrapper isPrivate />}>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<Profile />} />
<Route path="/customers" element={<Customers />} />
<Route path="/new" element={<New />} />
<Route path="/new/:id" element={<New />} />
</Route>
</Routes>
);
See Layout Routes for further details.
You should convert
<Routes>
<Route exact path="/" element={Dashboard} />
</Routes>
to
<Routes>
<Route exact path="/" element={<Dashboard/>} />
</Routes>
Also if you want to keep your UI in sync with the URL use like this.
<BrowserRouter>
<Routes>
<Route exact path="/" element={<Dashboard/>} />
</Routes>
</BrowserRouter>
Best.

React Router v6 error: All component children of <Routes> must be a <Route> or <React.Fragment>

The following React routes code probably works in React Router v5, but gives the following error in React Router v6
Error: [Player] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>
Is it possible to update the Routes/Route code so that it works in React Router v6?
function App() {
// Some stuff here...
const { players, offlinePlayers } = usePlayers();
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/players">
{players.map((player) => {
return (
<Route exact key={player.name} path={`/players/${player.name}`}>
<Player player={player} />
</Route>
);
})}
</Route>
</Routes>
</BrowserRouter>
</ThemeProvider>
)
}
The Player component should be rendered by a Route component on the element prop, not as a child of the Route.
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/players">
{players.map((player) => (
<Route
key={player.name}
path={`/players/${player.name}`}
element={<Player player={player} />}
/>
))}
</Route>
</Routes>
</BrowserRouter>
You should map Routes in their parent route.
Like:
<Route path="/players">
{players.map((player) => (
<Route exact key={player.name} path={`/players/${player.name}`}>
<Player player={player} />
</Route>
);
)}
</Route>
But if you want to render dynamic player then dont use the above code for that purpose because its not best approach if you are using dynamic player.name. In your code you are creating each route for every player.
So, use the following code:
<Route path="/players">
<Route exact path={":playerName"} element={<Player/>} />
</Route>
And in Player component, extract playerName from params like:
let { playerName } = useParams();

Protected Routes and Nested Routes are not working together in react routing

My nested privateRoute is not working.
When I changed the nested routes in normal routing like /route everything works fine but as I switch to nested routes like /route/nested-route PrivateRoute component loses its functionality.
This is the main routing in my app component.
function App() {
return (
<Suspense fallback={<Loader />}>
<Switch>
<Route path="/" exact={true} component={Home} />
<Route path="/login">
<ProvideAuth>
<Login />
</ProvideAuth>
</Route>
<Route path="/signup">
<SignUpProvider>
<SignUp />
</SignUpProvider>
</Route>
<PrivateRoute path="/home" component={UserHome} />
<PrivateRoute path="/profile" component={UserProfile} />
<Route path="*" component={Error} />
</Switch>
</Suspense>
);
}
PrivateRoute Component works fine in this main routing.
This is code of nested and protected route.
const UserHome = () => {
const { url } = useRouteMatch();
return (
<Wrapper>
<Header />
<Suspense fallback={<Loader />}>
<Switch>
<PrivateRoute exact path={`${url}/feed`} component={Feed} />
<PrivateRoute path={`${url}/search`} />
<PrivateRoute path={`${url}/addUser`} />
<PrivateRoute path={`${url}/notification`} />
<PrivateRoute path={`${url}/profile`} component={UserProfile} />
</Switch>
</Suspense>
<BottomNavbar />
</Wrapper>
);
};
PrivateRoute is not working in this nested routing. The redirect route is not firing when the user logs out.
Code for PrivateRoute Component.
const PrivateRoute = ({ children, ...rest }) => {
const cookie = document.cookie;
return (
<Route
{...rest}
render={() => {
return cookie ? children : <Redirect to="/login" />;
}}
/>
);
};
I think the issue here is that the cookie value is computed once, initially, when the PrivateRoute component is mounted and rendered. It's a stale value from the enclosure.
Move the cookie into the returned render function so it's accessed when the path is matched.
const PrivateRoute = ({ children, ...rest }) => (
<Route
{...rest}
render={() => document.cookie ? children : <Redirect to="/login" />}
/>
);

Default routing in react-router-dom not working, when using conditional rendering

I have the following React component:
function Routes(props) {
const { role, userId } = props;
const renderMemberTasksPage = (props) => {
//Redirects to 404 or not
};
const renderTracksPage = (props) => {
//Redirects to 404 or not
};
return (
<>
<Switch>
<Route exact path='/members/:id/tasks' render={renderMemberTasksPage} />
<Route exact path='/members/:id/tasks/id:open?' render={renderMemberTasksPage} />
{role === 'admin' && (
<Route path='/members/new'>
<NewMember />
</Route>
)}
{(role === 'admin' || role === 'mentor') && (
<>
<Route exact path='/'>
<Redirect to='/members' />
</Route>
<Route exact path='/members'>
<MembersManagerPage />
</Route>
<Route exact path='/tasks/'>
<MemberTasksPage />
</Route>
<Route path='/tasks/new'>
<NewTask />
</Route>
<Route exact path='/tasks/id:open?'>
<MemberTasksPage />
</Route>
<Route path='/tasks/id:open/edit'>
<MemberTasksPage edit />
</Route>
<Route path='/members/:id/progress'>
<MemberProgressPage />
</Route>
</>
)}
{role === 'member' && (
<>
<Route exact path='/'>
<Redirect to={`/members/${userId}/tasks`} />
</Route>
<Route path='/members/:id/tracks' render={renderTracksPage} />
</>
)}
<Route exact path='/404'>
<Error404Page />
</Route>
<Route path='*'>
<Redirect to='/404' />
</Route>
</Switch>
</>
);
}
In simple words, this code defines routes depending on the current user role. Here I've got a problem: default router * is not working. Different order of routes and using exact in different combinations showed no results. When I removed all routes, rendered conditionally, it worked. Can it be the reason and how to avoid such behavior?
My version of react-router-dom:
"react-router-dom": "^5.1.2"
I think you can remove the path='*' and put directly the component inside the <Route>
It would be something like that:
<Route>
<Error404Page />
</Route>
since the Switch will try to match every path the last one will be used if it can't find any

Can't pass a function to a component via <Route />

setErm is a function and is undefined in Erm component. Although the App component receives it. If I pass anything like something='foo' the ERM component get's it but not setErm={props.setErm}
const App = props => {
console.log("props in App", props);
return (
<Router>
<div>
<Switch>
<Route exact path="/" component={Home} />
<Route
path="/erm"
render={props => <Erm {...props} setErm={props.setErm} />}
/>
<Route exact path="/:weekId" component={Week} />
<Route exact path="/:weekId/:dayId" component={Day} />
</Switch>
</div>
</Router>
);
};
The problem in your code is that in your Erm component props here refer to the Route component props not App props so you can change your code to use the props of App to something like this:
const App = props => {
console.log('props in App', props);
const appProps = props; // add this line to refer to App props
return (
<Router>
<div>
<Switch>
<Route exact path="/" component={Home} />
<Route
path="/erm"
render={props => <Erm {...props} setErm={appProps.setErm} />}
/>
<Route exact path="/:weekId" component={Week} />
<Route exact path="/:weekId/:dayId" component={Day} />
</Switch>
</div>
</Router>
);
};
Or you can use destructuring:
const App = ({ setErm }) => {
console.log('props in App', props);
return (
<Router>
<div>
<Switch>
<Route exact path="/" component={Home} />
<Route
path="/erm"
render={props => <Erm {...props} setErm={setErm} />}
/>
<Route exact path="/:weekId" component={Week} />
<Route exact path="/:weekId/:dayId" component={Day} />
</Switch>
</div>
</Router>
);
};
As you can see you now able to use the top props of App component

Categories

Resources