On React Router, stay on the same page even if refreshed - reactjs

my site is built using MERN stack, when I refresh a page it first shows the main page and then the page where the user is. How to fix this issue?
For example:
if I refresh (/profile) page then for a meanwhile it shows (/) then it redirects to (/profile). I want if I refresh (/profile) it should be on the same page.
import { Route, Redirect } from 'react-router-dom';
const PrivateRoute = ({ component: Component, authed, ...rest }) => {
return (
<Route
{...rest}
render={(props) => authed === true
? <Component {...props} />
: <Redirect to={{ pathname: '/', state: { from: props.location } }} />}
/>
)
}
export default PrivateRoute;
Router code:
const App = () => {
const user = useSelector((state) => state?.auth);
return (
<>
<BrowserRouter>
<Container maxWidth="lg">
<Switch>
<Route path="/" exact component={Home} />
<Route path="/about" exact component={About} />
<Route path="/terms" exact component={Terms} />
<PrivateRoute authed={user?.authenticated} path='/profile' component={Profile} />
</Switch>
</Container>
</BrowserRouter>
</>
)
}
export default App;
How to fix so that user stays on the same page if its refreshed? The issue is on the pages where authentication is required.

When first authenticated the user, store the credentials(the info that you evaluate to see if the user is authenticated. Tokens etc.) in the localStorage. Of course you have to create necessary states too.
Then with useEffect hook on every render set the credential state from localStorage.
function YourComponentOrContext(){
const[credentials, setCredentials] = useState(null);
function yourLoginFunction(){
// Get credentials from backend response as response
setCredentials(response);
localStorage.setItem("credentials", response);
}
useEffect(() => {
let storedCredentials = localStorage.getItem("credentials");
if(!storedCredentials) return;
setCredentials(storedCredentials);
});
}

I guess on mounting (=first render) your user variable is empty. Then something asynchronous happen and you receive a new value for it, which leads to new evaluation of {user?.authenticated} resulting in true and causing a redirect to your /profile page.
I must say I'm not familiar with Redux (I see useSelector in your code, so I assume you are using a Redux store), but if you want to avoid such behaviour you need to retrieve the right user value on mounting OR only render route components when you've got it later.

Related

How to run useEffect before render for authentication

I have reading component which must show only when user is loggedIn. I now redirect the user to /login page if the user is not authenticated. But during the redirect, the reading page is displayed for a few milli seconds allowing the un-authenticated user to see a flickering of the reading page during redirection.
I tried using useLayoutEffect instead of useEffect but the same flickering happens. Also tried without using useEffect or useLayoutEffect and within a function, but still the same
I get the userInfo from a global state, which gets the userInfo from the cookie. For state management I use recoil.
Reading Page: (which must be protected and if no user, redirect to login page)
function index() {
const router = useRouter();
const userInfo = useRecoilValue(userAtom); ---> userInfo is recoil global state
useLayoutEffect(() => {
if (!userInfo) {
router.push("/login?redirect=/reading");
}
}, []);
return (//some code)
Note:
Adding a Loader worked, but is there any better way?
Check the authentication state
show loader based on authentication state
Redirect the user
I would suggest a much better way. Instead of checking on individual pages.
You can write your Auth Check for user at route level i.e inside index.js where the React-Router is defined.
// PrivateRoute.jsx
function PrivateRoute ({component: Component, isAuth, ...rest}) {
return (
<Route
{...rest}
render={(props) => isAuth
? <Component {...props} />
: <Redirect to={{pathname: '/login', state: {userInfo} }} />}
/>
)
}
// index.jsx
.
const userInfo = useRecoilValue(userAtom);
.
<Route path='/login' component={Login} />
<Route path='/register' component={Register} />
<PrivateRoute isAuth={!!userInfo} userInfo={userInfo} path='/dashboard' component={Dashboard} />
.
.
Hope this finds it helpful.

How do I correctly implement a redirect?

I made it so that if the user is already logged in and he tried to go to the login page, then he is redirected to the main page. But when sign out, the redirection occurs only after the page is refreshed (also when sign in). How to fix it?
route:
const isLoggedIn = localStorage.getItem("token");
return (
<BrowserRouter>
<Route path={'/'} component={Home} />
<Route path={'/sign-in'} exact render={() => (isLoggedIn ? (<Redirect to="/" />) : (<SignIn />))} />
</BrowserRouter>
);
sign-out:
const signOut = async () => {
localStorage.removeItem('token')
await axios.post('sign-out', {});
setRedirect(true);
}
if (redirect) {
return <Redirect to="/sign-in" />
}
Try using a state in your component and set state according to your login status.Once the state is changed component will re-render automatically.
or else try using a global state like context or redux which will ease your process.

BrowserRouter runs Before useEffect on ReactJS

My problem is that React will check first Routing for my page and afterwards is going to run useEffect() function. This gives me kind of a problem. What im doing is that i need useEffect() to run first in order to fetch data from local storage. That specific data "decides" in which page my application will redirect.
So that's the main App.js
function App() {
const [user, setUser] = useState({id:null,authorized:false});
useEffect(() => {
const aUser = JSON.parse(localStorage.getItem("user"));
if(aUser!=null){
console.log(JSON.stringify(aUser));
setUser(aUser);
}
}, [])
return (
<div className="App">
<BrowserRouter>
<Route exact path="/" render={ () =>
!user.authorized ? <Login setUser={setUser} user={user}/>
: <Redirect to="/home" /> }
/>
<Route exact path="/login" render={ () =>
!user.authorized ? <Login setUser={setUser} user={user}/>
: null}/>
<Route exact path="/home"
render={() => !user.authorized ?
<Redirect to="/login" /> : <Forbidden/>
}/>
</BrowserRouter>
</div>
);
}
export default App;
So let's assume that user data is already stored in local storage and the user is authorized. When i start up my react app it will first show the Home page and that's because i have set the Route "/" to redirect to "/home" if the user is authorized. Ok that good.
My "problem" is when i refresh the page from /home it will redirect to /login.
Why? Because Routes will be checked first and after that the useEffect() will run.
An obvious solution will be to redirect to /home as i do for the first Route ("/").
OK i get that but why useEffect() Won't run first? That's my main question.
Update:
I can solve MY problem from the beginning but i want to know if there is a solution regarding useEffect().
One solution is as i said before to redirect to "/home" if user is authorized like that
<Route exact path="/login" render={ () =>
!user.authorized ? <Login setUser={setUser} user={user}/>
: <Redirect to="/home" />
}
/>
Basically the same code as "/" Route.
Another solution that may be the best it's to get rid of useEffect() and load the user data with useState() like this:
const [user, setUser] = useState(()=>localStorage.getItem("user"));
The way you can solve this problem is by initializing the state itself by fetching from local storage like this:
const [user, setUser] = useState(()=>localStorage.getItem("user"));
useState takes a function that can be used to initialize the state. This is used for lazy intialization.
Link to Docs
It's also good to keep in mind the order in which the different lifecycle hooks run.

Redirecting when user is authenticated

I am trying to build a role based access control React app.
My vision was that when the App mounts, it checks if user token exists. If it does, it runs checkAuthToken() and sets the state accordingly.
Where I am struggling is: redirection doesn't work as I expect it to.
Here is my code:
in App.js
function App() {
const { isAuthenticated, user } = useSelector(state => {
return state.userState;
});
const dispatch = useDispatch();
useEffect(() => {
checkAuthToken();
}, []);
return (
<Router>
<Switch>
<Route exact path='/'>
{!isAuthenticated ? (
<Redirect to='/login'/>
) : (
<Redirect to={`/${user.role}`} />
)}
</Route>
<Route
path="/login"
render={() => {
return <Login />;
}}
/>
<Route
path="/admin"
render={() => {
return <AdminDashboard user={user} />;
}}
/>
<Route
path="/staff"
render={() => {
return <OrderMenu user={user} />;
}}
/>
<Route component={ErrorPage} />
</Switch>
</Router>
);
}
export default App;
My understanding is React rerenders when state or props change. When I get the updated state from Redux, the app component should rerender and thus go through the isAuthenticated check and redirect accordingly, but i'm being proven wrong.
Currently, if there's no token, it redirects to Login route. After user logs in, token is set in localStorage. So to test it out, I close and open a new tab, try to go to path / expecting it to redirect me to /[admin|staff] route since checkAuthToken would successfully set the state but redirect doesn't work and just lands on /login. However, I could access /[admin|staff] if I type in manually.

React Router v4 - Redirect to home on page reload inside application

I need to redirect to home page when user refreshes other pages inside my application. I am using React router v4 and redux. Since the store is lost on reload, the page user reloaded is now empty and hence I want to take him back to a page that does not need any previous stored data. I don't want to retain state in localStorage.
I tried to handle this in event onload but it did not work:
window.onload = function() {
window.location.path = '/aaaa/' + getCurrentConfig();
};
You can try creating a new route component, say RefreshRoute and check for any state data you need. If the data is available then render the component else redirect to home route.
import React from "react";
import { connect } from "react-redux";
import { Route, Redirect } from "react-router-dom";
const RefreshRoute = ({ component: Component, isDataAvailable, ...rest }) => (
<Route
{...rest}
render={props =>
isDataAvailable ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/home"
}}
/>
)
}
/>
);
const mapStateToProps = state => ({
isDataAvailable: state.reducer.isDataAvailable
});
export default connect(mapStateToProps)(RefreshRoute);
Now use this RefreshRoute in your BrowserRouter as like normal Route.
<BrowserRouter>
<Switch>
<Route exact path="/home" component={Home} />
<RefreshRoute exact path="dashboard" component={Dashboard} />
<RefreshRoute exact path="/profile" component={ProfileComponent} />
</Switch>
</BrowserRouter>
It is so amazing that you don't want to keep state of user route map in browser but you use react-router!, the main solution for your case is do not use react-router.
If you don't use it, after each refresh the app come back to main view of app, If you wanna see route map in address bar without any reaction use JavaScript history pushState.
Hope it helps you.

Resources