Protected routung in Reactjs - javascript

Aim : I have user logged in as ADMIN, user is in localstorage. Now i want to check
if User === "admin" return the Component else Redirect to main page.
Problem : Whenever i try to access the component (logged in as ADMIN) it Redirects me to main application page. What it should do is to let me in.
Here is the code, i think the problem is in If statement.
function App() {
const cart = useSelector((state) => state.cartReducer.cart);
const user = JSON.parse(localStorage.getItem("profile"));
return (
<Router>
<Switch>
<Route path="/updateProduct/:name/:id">
<Navbar cart={cart} user={user} />
if(user?.result?.role !== "admin") {<Redirect to="/" />} //here it is misbehaving
<ProductUpdate />
<Footer />
</Route>
</Switch>
</Router>
);
}
export default App;
Note. I have multiple components here, So how do i do it.

As far as i concern, you need to use ternary operator or short circuit inside route.
You can use this by consoling the output.
if(user?.result?.role !== "admin") {
console.log('here');
<Redirect to="/" />};
}
check if 'here' is showing in the console.

Related

Map Components not rerendered after navigate back

i have a page with the following code:
// Homepage
return (
<>
<div>HELLO BE YOu</div>
{list.map((video) => {
return <VideoPreview key={video._id} video={video</VideoPreview>
});}
{list.map((video) => {
return <div>TEST</div>
})}
</>
);
VideoPreview is an imported component:
export const VideoPreview = ({video}) => {
const navigate = useNavigate();
function handleClick(){
navigate('/video');
}
return <div onClick={handleClick}>video</div>
}
When a user clicks on <VideoPreview/>, they will be directed to another page which has been defined in App.js to be
<BrowserRouter forceRefresh={true}>
<Routes>
<Route path="/" element={<Homepage />} />
<Route path="/video" element={<Videopage />} />
</Routes>
</BrowserRouter>
The bug is that when the user attempts to go back to "/" path from "/video" path, the HomePage component does not render properly.
The items inside the list map do not render. (Other element outside of list.map eg, <div>HELLO BE YOu</div> was rendered properly though). I have verified that list is not empty and when i place console.log statements within the map function, those logs gets printed out.
{list.map((video) => {
return <VideoPreview key={video._id} video={video}></VideoPreview>
});}
{list.map((video) => {
return <div>TEST</div>
})}
May i get some help in resolving this problem? Thank you.

react-dom-router Link component doesnt work as expected

I have a react app with two pages--- the home and trip page. The trip page is rendered with the tripID passed in through the url. My app.js looks like :
function App() {
return (<>
<ThemeProvider theme={theme}>
<Router>
<Routes>
<Route exact path='/' element={<Home />} />
<Route path='/trip/:tripId' element={<TripComponent />} />
</Routes>
</Router>
</ThemeProvider>
</>
);
}
I have a global navbar with a menu of different tripIds as many Link to navigate to the TripComponent. When i am at the "/" path, and I navigate to "/trip/tripA", i have no problems. But when i am at "/trip/tripA" , and i want to navigate to "/trip/tripB", it doesnt work. I can see the url changing accordingly, but my TripComponent doesnt rerender with tripB's data. the code for my menu in my navbar looks like:
ReactDOM.createPortal(<>
<CustomModal setOpen={setShowTripList} title={"Saved Trips"}>
<List>
{trips && trips.length > 0 &&
trips.map((trip, index) => {
return (
<Link to={`/trip/${trip._id}`} >
<ListItemButton>
<ListItemText id={trip._id} primary={trip.title} />
</ListItemButton>
</Link>
)
})
}
</List>
</CustomModal>
</>
, document.getElementById("portal"))
I am confused as to why this is happening. When i press the Link to navigate to another URL, shouldn't it unmount and remount?
When the tripId route path parameter updates the routed element TripComponent isn't remounted, it is only rerendered. If there is some logic that depends on the tripId path parameter then TripComponent needs to "listen" for changes to that value. This is the same thing that would need to happen if a prop value changed.
Use a useEffect hook with a dependency on the tripId path parameter to issue side-effects based on the current value.
Example:
import { useParams } from 'react-router-dom';
...
const TripComponent = () => {
const { tripId } = useParams();
useEffect(() => {
// initial render or tripId updated
// do something with current tripId value
}, [tripId]);
...
};
I think the #Drew's answer is perfect.
But I'd like to put something additionally.
I suggest you to use useMemo hook.
...
const trip = useMemo(() => {
// something you want
}, [tripId])
...

React-Router render : Functions are not valid as a React child

I have created a HOC "ProtectedRoute" to restrict paths for unauthenticated users. I have used "react-router-dom" for routing in the application.
I am able to route the users based on their token but I am receiving a warning in the console and I am unable to access routerprops such as "history", "location", "match" in rendered component. Below is the ProtectedRoute component implementation.
const ProtectedRoute = ({component:Component, ...rest}) => {
const [isLoggedIn, setLoggedIn] = useState({loggedIn:false, loaded:false})
useEffect(async () => {
const userStatus = await validateUserLoggedIn(); # returns true if user is authenticated
setLoggedIn((prevState) => {return {loggedIn:userStatus, loaded:true}})
}, [])
return(
<Route {...rest} render={(routerProps) => {
return isLoggedIn.loaded ?
isLoggedIn.loggedIn ? <Component {...routerProps} {...rest} /> : <Redirect to={{pathname:'/login'}} :
<h1>Loading Page</h1>
}} />
)
}
ProtectedRoute in main routing component:
<Switch>
<ProtectedRoute exact path="/admin" component={Admin} />
</Switch>
Error Message "Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it".
Any suggestion is appreciated.
Ok so there a few things wrong.
You should not return something inside a component, you are doing this inside your <Route /> component. This is causing the warning: Functions are not valid as a React child. This may happen if you return a Component instead of from render.
Your <Redirect /> component was missing a /> at the end of it, leaving it open will cause an error.
These fixes should also solve your problem where you couldn't access the route props.
<Route
{...rest}
render={(routerProps) => {
isLoggedIn.loaded ? (
isLoggedIn.loggedIn ? (
<Component {...routerProps} {...rest} />
) : (
<Redirect to={{ pathname: '/login' }} />
)
) : (
<h1>Loading Page</h1>
);
}}
/>;

How to prevent PrivateRoutes stored in router stack from re-rendering after a user logs out?

I have an application with dynamic tabs that show different tabs depending if the user is authenticated or not. Naturally, I have created a PrivateRoute component to protect against users accessing certain pages while unauthenticated. However, I've discovered if the user logs in, visits an authenticated page, and then logs out, the PrivateRoute is still triggered even though the component no longer exists in DOM.
This is a problem because now that the user is not authenticated, the PrivateRoute still triggers and Redirects to the auth URL (which is the fallback for unauthenticated users in the PrivateRoute component).
Video example: https://streamable.com/qviin2
Code reproduction: https://codesandbox.io/s/pensive-voice-1w3rc?file=/src/App.tsx
Steps to reproduce:
Visit the Log In Tab
Click Log In
Click Tab 2
Click Log Out
Observe how you are unable to click to change any tab and how the private route is still triggered when changing tabs in the console.
(Also it's interesting to note that the Tab2 page shows, but the URL is incorrect and the tab that you click on doesn't change)
Any guidance on how to remove private routes from a router stack after logout (without a page refresh) would be GREATLY appreciated!! Thanks.
Here's my PrivateRoute:
const PrivateRoute: React.FC<PrivateRouteProps> = ({
component: Component,
...rest
}) => {
const { user, loading } = useAuth();
console.log("PRIVATE ROUTE [LOADING]", loading);
return (
<Route
{...rest}
render={(props) => {
return (
<>
{loading ? (
<Loading />
) : user ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: `/auth`,
state: {
from: props.location.pathname
}
}}
/>
)}
</>
);
}}
/>
);
};
My App.tsx contains the following routes:
<Route exact path="/tab2" component={Tab2} />
<Route path="/:tab(auth)" component={Login} exact />
<PrivateRoute path="/:tab(account)" component={Account} />
<PrivateRoute
path="/:tab(account)/settings"
component={Settings}
exact
/>
<Route exact path="/">
<Redirect to="/tab1" />
</Route>
On login I call an AuthProvider method to set a Userobject and route to aPrivateRoute`:
<IonButton
disabled={loading}
onClick={async () => {
await onAuthenticate();
push("/account");
}}
>
Log In
</IonButton>
And on Logout, I set the User object to undefined using the AuthProvider and try to route away from the PrivateRoute:
<IonButton
onClick={async () => {
await onLogout();
push("/tab2");
}}
>
Log out
</IonButton>

What's the best way to redirect a user to a page only if certain conditions are valid?

I'm using React for my SPA. I know how Routes work, and I know how to make a PrivateRoute.
The problem is that I need to verify my users identity before they being able to use a PrivateRoute. I tried to implement it like this:
function PrivateRoute({ component: Component, ...rest }) {
return (
<Route
{...rest}
render={props => {
if (isAuthenticated()) {
if (isVerified()) {
return <Component {...props} />;
} else if (isInProcess()) {
// replace url and render verification component
history.push("/inverification");
} else {
// replace url and render document upload
history.push("/documentupload");
return <DocumentUpload />;
}
} else {
return (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
);
}
}}
/>
);
}
export default function Routes() {
// manage mobile drawer
const [drawer, setDrawer] = useState(false);
return (
<Suspense fallback={<Loader />}>
<Router history={history}>
<div className="root-div">
<Navbar setDrawer={setDrawer} />
<Drawer drawer={drawer} setDrawer={setDrawer} />
<div className="content-div">
<Switch>
<Route exact path="/" component={Main} />
<Route exact path="/login" component={Login} />
<Route
path="/login/callback"
component={Callback}
/>
<PrivateRoute path="/signup" component={Signup} />
<Route path="/logout" component={Logout} />
<PrivateRoute
path="/test"
component={() => <h1>Success</h1>}
/>
</Switch>
</div>
<Footer />
</div>
</Router>
</Suspense>
);
}
So when logging in, I save a cookie with some information about my user, encoded in a JWT token.
isVerified() and isInProcess() check if the user is already able to use the PrivateRoute. If isVerified() is true, then he can proceed to the component, else it checks if the user is in the process of being verified.
What I need to do is:
Right after the user signup, the form saves the user info and push the history to /documentupload. I need this component to render ONLY if isVerified() and isInProcess() are false.
If the user already submitted his documents and isInProcess() is true, than I need to push to /inverification and render another component, but ONLY if isInProcess() is true and isVerified() is false.
For both the above cases, isAuthenticated() must be true.
The way I implemented don't work, it renders a blank page instead of rendering the <DocumentUpload /> component.
My question is: what's the best way to achieve what I need?
I think that creating another function, like function DocumentUploadVerification similar to the PrivateRoute function is a way, but I guess it will be too verbose, and there might be a better way to solve it.
What are your thoughts?
Thanks in advance!
Your component never rerenders again. in this case you can repeat isInProcess|isVerified checks or use state manager like redux for storing current state for isInProcess|isVerified
const [_isInProcess, setIsInProcess] = useState(false);
useEffect(() => {
const i = setInterval(() => {
if (isInProcess()) {
setIsInProcess(true);
clearInterval(i);
}
}, 200);
clearInterval(i);
}, []);
When you do:
history.push("/documentupload");
return <DocumentUpload />;
you will be switched to the /documentupload route that doesn't exist in your Routes component and that is the reason you are seeing the blank page. What I would do is:
In routes add a new entry:
<PrivateRoute path="/documentupload" component={DocumentUpload} />
and remove return from PrivateRoute. This way DocumentUpload will be "protected" + history.push will save you from a recursion problem since when you try to push the location you are already on it will just ignore it(if you don't like the warning you can always add a check if you are already in that location).
Hope this helps.

Categories

Resources