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.
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>
);
};
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 a login page which is a public route and a dashboard page which is a private route as follows:
App.js:
function App() {
return (
<Router>
<div className="App">
<Switch>
<PublicRoute path="/" exact component={Login} />
<PrivateRoute path="/dashboard" component={Dashboard} />
</Switch>
</div>
</Router>
);
}
And this is my private route component:
let store = require("store");
const PrivateRoute = ({component: Component, ...options}) => {
// const finalComponent = user != null && user.auth == true ? component : Login;
const [userData, setUserData] = useState({});
useEffect(() => {
setUserData(store.get("userData"));
}, []);
return (
<Route
{...options}
render={(props) =>
store.get("userData") ?
<Component {...props} />
:
<Redirect to="/"/>
}
/>
);
};
export default PrivateRoute;
Now the problem that I am facing is that if I open the dashboard localhost:9999/dashboard none of my components are visible because they are routed inside the dashboard component.
So when I open this localhost:9999/dashboard I want react to redirect to localhost:9999/dashboard/home and also show the components if the user is logged in.
I tried to do this :
return (
<Route
{...options}
render={(props) =>
store.get("userData") ?
<Redirect to="/dashboard/home" component={<Component {...props}} />
:
<Redirect to="/"/>
}
/>
);
This did route to the home page but nothing was visible at all. I mean the components inside dashboard weren't visible at all as well.
This is what I am getting. You see it redirects but doesn't show anything:
I believe that you could be more specific about which file is the code that you are showing.
But I feel that what you are trying to accomplish something like this:
PrivateRouter
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
const PrivateRoute = ({component: Component, restricted, ...rest}) => {
return (
<Route {...rest} render={props => (
user.auth == true ?
<Redirect to="/dashboard" />
: <Redirect to="/dashboard/home" />
)} />
);
};
export default PrivateRoute;
Is it possible in React to conditionally render a inside a using this kind of component?
import PropTypes from 'prop-types'
import { useApplication } from 'ice-components'
const canEdit = (currentUser) => {
if (currentUser.settings.canEditSpecRec.value === 'False' || !currentUser.location.isSpecimenReceptionType) {
return false
}
return true
}
const Permissions = ({ children }) => {
const { currentUser } = useApplication()
if (!canEdit(currentUser)) {
return null
}
return children
}
Permissions.propTypes = {
children: PropTypes.node,
}
export { Permissions as default, canEdit }
And the route is made in this way:
<Switch>
<Route exact path component={component_1} />
<Route path='/anotherPath' component={component_2} />
<Switch>
I tried to wrap the Permissions component around a single Route component but it breaks. Any ideas?
A good way to conditionally render a Route is the one provided by the section Auth of the react-router documentation:
https://reacttraining.com/react-router/web/example/auth-workflow
What's different in your application is that you're not authorizing based on authentication but on some user permission, you could do something like this:
function PrivateRoute({ children, ...rest }) {
return (
<Route
{...rest}
render={({ location }) =>
canEdit(currentUser) ? (
children
) : (
<Redirect
to={{
pathname: "/",
}}
/>
)
}
/>
);
}
Wrapping the Route in a PrivateRoute component that will return your route if user has permission to or something else if not (maybe a redirect).
export default function AuthExample() {
return (
<Router>
<div>
<Switch>
<Route path="/public">
<PublicPage />
</Route>
<Route path="/login">
<LoginPage />
</Route>
<PrivateRoute path="/protected">
<ProtectedPage />
</PrivateRoute>
</Switch>
</div>
</Router>
);
}
I am using the ConnectedRouter component and a custom Route like so:
const PrivateRoute = ({ layout: Layout, component: Component, rest }) => (
<Route
{...rest}
render={matchProps => (
<AuthConsumer>
{({ authenticated }) =>
authenticated ? (
<Layout pathname={matchProps.location.pathname}>
<Component {...matchProps} />
</Layout>
) : (
<Redirect to={{ pathname: '/login', state: { from: matchProps.location } }} />
)
}
</AuthConsumer>
)}
/>
)
which I use like this:
<Switch>
<PrivateRoute
exact
path={`${match.url}someroute/:id`}
layout={Layout}
component={SomePage}
/>
</Switch>
And a component like so:
import React from 'react'
const SomePage = ({ match }) => {
console.log('MATCH ', match.params)
return <div>TESTING</div>
}
export default SomePage
In this case, match is empty, and it thinks its on the '/' route (even though location prop says its on /someroute/123.
On the other hand, this works:
<Route
exact
path={`${match.url}someroute/:id`}
component={SomePage}
/>
and match gets updated properly.
I'm stumped as to why this is happening. Any help would be appreciated!
Ah, I figure it out. rest should have been ...rest, the <Route> component wasn't getting the props passed down correctly!