I am using react router to navigate between different pages. Everytime I navigate to another page the state must be updated based on the new path. I use a context to provide this state for all components. My code looks like this:
export const App = () => {
return (
<MyContextProvider>
<Router>
<Switch>
<Route path="/path1" render={(props) => {
const mode = props.match.path.replace('/', '')
return <DataImportWrapper mode={mode} />
}
}/>
</Switch>
</Router>
</MyContextProvider>
)
}
const DataImportWrapper = ({mode}) => {
const { setMode } = useContext(MyContext)
setMode(mode)
return <DataImport />
}
It is very important that the state is updated BEFORE rendering any components of another route because all subcomponents are using that state. With this code I get the warning: Cannot update a component ('MyContextProvider') while rendering a different component ('DataImportWrapper'). and also my state is not updated. Does anyone know what I am doing wrong?
What I would suggest is to set the mode in the Contextprovider and move the Contextprovider within the router. This way you don't need to create an extra wrapper and the mode is always set before rendering other components. You can the set the mode in your ContextProvider
const MyContextProvider = ({ children }) => {
const { path } = useRouteMatch("/path1");
return (
<MyContext.Provider value={{ mode: path.replace('/', '') }}>
{children}
</MyContext.Provider>
)
}
export const App = () => {
return (
<Router>
<Switch>
<MyContextProvider>
<Route path="/path1" ><DataImport /></Route>
</MyContextProvider>
</Switch>
</Router>
)
}
Related
So this might be hard to get at first but I'll try to explain everything possible. I'm rendering an App component which uses useNavigation hook from react-router-dom library. Inside AppRoutes I check, if I have $stateParams.redirect and also other values like param1 and param2.I get $stateParams from another custom hook defined in my app. While running the app, I get the log, should navigate now but it actually doesn't navigate to decider-route page instead it stays at / which is <Home /> component. Also I have this warning in console You should call navigate() in a React.useEffect(), not when your component is first rendered. I was wondering why doesn't the navigation takes place to decider-route and the warning is the reason why navigation does not take place?
const App = () => {
return (
<MemoryRouter>
<AppRoutes />
</MemoryRouter>
)
}
const AppRoutes = () => {
const navigate = useNavigate() // react-router-dom v6
if ($stateParams.redirect) {
if ($stateParams.param1 && $stateParams.param2) {
console.log('StateParams : ', $stateParams)
console.log('Should navigate now!')
navigate(`/decider-route/${$stateParams.param1}/${$stateParams.param2}`)
}
}
return (
<Routes>
<Route path="/" element={<Home />} />
<Route
path="/decider-route/:param1/:param2"
element={<Component />}
/>
</Routes>
)
}
The error is preety much self-explanatory. You just need to wrap the navigate() in a useEffect() hook so that it gets executed after the component mounts.
But, in this case, it is being called as soon as the component is first rendered.
navigate() should be triggered by a user action or an useEffect hook in this case. But you're not playing by the rules :)
app.js
const App = () => {
return (
<MemoryRouter>
<AppRoutes />
</MemoryRouter>
);
};
const AppRoutes = () => {
const navigate = useNavigate(); // react-router-dom v6
useEffect(() => {
if ($stateParams.redirect) {
if ($stateParams.param1 && $stateParams.param2) {
console.log("StateParams : ", $stateParams);
console.log("Should navigate now!");
navigate(
`/decider-route/${$stateParams.param1}/${$stateParams.param2}`
);
}
}
}, []);
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/decider-route/:param1/:param2 " element={<Component />} />
</Routes>
);
};
I have a React app which doens't have a route to / setup, so I placed a redirect inside the Switchcomponent, so whenever the user tries to access home they are redirected to the UserHome component.
The Switch set-up is as follows:
const AppRoutes = () => (
<Switch>
<Redirect
exact
from="/"
to={ROUTES.CITIZEN}
/>
<Route
exact
path="/login"
component={Login}
/>
<AuthenticatedRouteRedirect
path={ROUTES.CITIZEN}
component={UserHome}
/>
<AuthenticatedRouteRedirect
path={ROUTES.ADMIN_REPORT_LIST}
component={reportList}
/>
<Route
path="/404"
component={ErrorView}
/>
<Route
component={ErrorView}
/>
</Switch>
And AuthenticatedRouteRedirect as follows:
const AuthenticatedRouteRedirect = ({
component: Component, path = '', exact = false, ...rest
}) => {
const { user } = useAuth();
return (
<Route
path={path}
exact={exact}
render={() => (user
? <Component {...rest} />
: <Redirect to="/login" />)}
/>
);
};
export default AuthenticatedRouteRedirect;
And my UserHome component:
const Dashboard = () => (
<>
<Navbar />
<Container>
<ActionMenu />
</Container>
</>
);
where ActionMenu component uses a custom hook called useReportsLocations, which its implementation is:
import React from 'react';
import { useHistory, useParams } from 'react-router-dom';
import { getMapPolygons, mexicoMap } from 'core/map-locations';
const useReportsLocations = (selectedGroup) => {
const { push } = useHistory();
const { state, municipality } = useParams();
const locationData = React.useMemo(() => {
const currentSelection = {
country: mexicoMap,
state,
municipality,
group: selectedGroup,
};
return getMapPolygons(currentSelection);
}, [municipality, selectedGroup, state]);
React.useEffect(() => {
if (!locationData?.stateData
|| !Object.keys(locationData?.stateData?.municipality
|| {}).includes(municipality)) {
push('/404');
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [municipality]);
return {
locationData,
state,
municipality,
};
};
export default useReportsLocations;
Here's a little demo on CodeSandBox
But when trying to access /, this error message is displayed with the next stacktrace, sorry if it's too large:
What is the cause for this to happen? And how to fix it?
I'm having a bad time trying to figure out whatever the cause is, but simply I can't.
Thank you for your answer and replies.
Pd
I finally figured out what's happening.
After I read the docs, as Redirect leads me to a route where some params are required, but I don't provide any ones to to (as seen in the ROUTES file), it has only the placeholders of {ROUTES.CITIZEN} route, and path-to-regexp#^1.7.0 will complaint that it has been passed nothing and coudn't not resolve to anything.
This behaviour is expected.
Here if I am hitting "/" or "/reset_password" or any other route always first component in PurelyUnauthenticatedRoutes gets called i.e. PublicRoute with message ROUTE_CONSTANTS.RESET_PASSWORD
<Router>
<Switch>
<PurelyUnauthenticatedRoutes />
<PublicRoute key={1} redirect={false} path="/" component={CheckAuthenticatedRoutes} />
</Switch>
</Router>
const PurelyUnauthenticatedRoutes = (props) => {
return [
<PublicRoute
{...props}
key={"/reset_password"}
redirect={false}
path={"/reset_password"}
render={(routeProps) => <div>{ROUTE_CONSTANTS.RESET_PASSWORD}</div>}
/>,
];
};
const PublicRoute = (props) => {
const { userAuthenticationDetails, redirect } = props;
const isLoggedIn = (userAuthenticationDetails && userAuthenticationDetails.isLoggedIn) || false;
if (isLoggedIn && redirect) {
return <Redirect to={"/dashboard"} />;
}
return <Route {...props} />;
};
export default PublicRoute;
It's the return statement on your PurelyUnauthenticatedRoutes component that's doing this. You are returning an array of PublicRoute components. React does not know how to render this properly. Instead, you should return multiple routes as children of a Switch component.
It's fine to have multiple Switch statements in a Router. All Route components need to be direct children of a Switch (unless you want to render multiple Routes).
You do need to figure some things out regarding your match conditions because the Switch at the top-level of the the Router means that traffic will go to either PurelyUnauthenticatedRoutes OR PublicRoute - not both. Currently all traffic gets handled by PurelyUnauthenticatedRoutes so that PublicRoute component with key={1} is never shown.
const PurelyUnauthenticatedRoutes = (props) => {
return (
<Switch>
<PublicRoute
{...props}
key={"/reset_password"}
redirect={false}
path={"/reset_password"}
render={(routeProps) => <div>{ROUTE_CONSTANTS.RESET_PASSWORD}</div>}
/>
</Switch>
);
};
Note: you aren't currently calling PurelyUnauthenticatedRoutes with any props when you use it in the App so you don't actually need to accept or pass down that props object if you don't want to.
I have built a PrivateRoute component in React that takes a given component and renders it if the user is logged in or not.
export default function PrivateRoute({component: Component,...rest}) {
return (
<SessionContext.Consumer > {
(context) => {
const {isLoggedIn} = context
return (
<Route {...rest}
render = {
props =>
isLoggedIn ? (
<Component {...props} />
) : (
<Redirect to = "/login" / >
)
}
/>
)
}
}
</SessionContext.Consumer>
);
}
Now in App.js I pass my private component like this:
<PrivateRoute exact path="/dashboard" component={Dashboard} />
Now the issue is when I want to pass a param in the url. This is how I do for the Route component:
<Route path="/user/:user_id" render={(props) =>
<UserComponent user={props.match.params.user_id} />
} />
This syntax unfortunately doesn't work when using my PrivateRouter.
I've tried several other approaches but I can't find a way to get the user_id in my UserComponent.
Am I missing something?
Your PrivateRoute uses prop component. But you pass component through render. if I understand your question correctly.
How do I set up conditional routes with exclusive Components in React?
I want the upload route to be exclusive to when authenticated, which is set in ComponentdDidMount.
render = () => {
let routes = (
<Switch>
<Route path = "/" exact render = {() => <Home />
<Route path = "/video" render = {() => <Video />} />
<Route path = "/upload" exact render = {() => <Upload />} />
<Redirect to = "/foo"/>
</Switch>
)
if(this.props.isAuthenticated){
routes = (
<Switch>
<Route path = "/" exact render = {() => <Dashboard />} />
<Route path = "/upload`" render = {() => <Upload />} />
<Route path = "/video" render = {() => <Video />} />
<Route path = "/foo" render = {() => <h1>Foo</h1>} />
<Redirect to = "/bar" />
</Switch>
)
}
return (
<div className="App">
<Layout>
{routes}
</Layout>
</div>
)
}
Right it's using the first set of Route components to check the route, and if the route doesn't match I get redirected to '/foo' which then renders the h1. If I try to access 'upload', I believe it gets rendered for a split second, and then I end up with infinite redirects to '/bar'. '/video' does render the video component. Can someone provide some information as to what may be going wrong and how I can make sure only one set of Routes is being used?
Maybe just need to handle authentication on private routes, you can get more info here
I had the same question a few months ago, and what I did is to create a wrapper to check if user has the necessary rights to see that item.
render: () => AuthorizeRoute(
Amortizations, // Component to load
'anyvalidRole4ex' // {string} constant
),
If you are using redux, could be something like this:
import React from 'react';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import NotAllowed from './NotAllowed';
export default function HasPermissionWrapper(
WrappedComponent,
requiredRoles,
FallbackComponent
) {
class HasPermission extends React.PureComponent {
static propTypes = {
userRoles: PropTypes.object.isRequired,
};
render() {
const userRoles = this.props.userRoles.toJS();
const hasPermission = userRoles
.map(({roleId}) => requiredRoles.includes(roleId))
.some((checks) => !!checks);
if (!hasPermission) {
if (FallbackComponent) {
return <FallbackComponent />;
}
return <NotAllowed userRoles={userRoles} />;
}
return <WrappedComponent {...this.props} />;
}
}
const mapStateToProps = ({auth}) => ({
userRoles: auth.getIn(['user', 'roles']),
});
return connect(mapStateToProps)(HasPermission);
}