I am trying to build an app with JWT authentication, express
as its backend and react
as its frontend. Used react-router
for frontend routing. What I am trying to do is, verify token of JWT
and if the token is verified then the server sends data back and based on the data I update my user state
. I used RTK query
for API calls.
When I try to access any routes its first go to the <PresistLogin />
components and it calls the useVerifyTokenQuery()
and on success it should dispatch(setUser(data))
and update the state.But, now the problem I am facing is when I access the state inside <RequiredAuth />
it returns null
. But the state should be updated by now. For user state being null, it navigate to /login
route but when it tries to access the /login
route, it first go to the <PresistLogin />
then go to the <RequiredAuth />
and this time it gets the data in user state
. Why it is not getting the updated state when it first access the <RequiredAuth />
.
This issue is causing redirection.
This is my code
My app routes
<Routes>
<Route element={<PresistLogin />}>
<Route element={<RequiredAuth />}>
<Route path="/" element={<Main />}>
<Route index element={<Home />} />
<Route path="apply">
<Route index element={<User.ApplyDoctor />} />
</Route>
<Route element={<AdminOnly />}>
<Route path="users">
<Route index element={<Admin.Users />} />
</Route>
<Route path="doctors">
<Route index element={<Admin.Doctors />} />
</Route>
</Route>
</Route>
<Route path="*" element={<div>404</div>} />
</Route>
<Route element={<NotRequireAuth />}>
<Route path="/login" element={<Login />} />
<Route path="/signup" element={<Signup />} />
</Route>
</Route>
</Routes>
verifyTokenQuery
verifyToken: builder.query({
query() {
return {
url: 'verifyToken',
method: 'GET',
credentials: 'include',
};
},
async onQueryStarted(args, { dispatch, queryFulfilled }) {
try {
const { data } = await queryFulfilled;
const user = await cookieExtractor(data.data.accessToken);
dispatch(setUser(user));
} catch (error) {
console.log(error);
}
},
}),
PresistLogin
import React from 'react';
import { Outlet } from 'react-router-dom';
import { useVerifyTokenQuery } from '../../redux/api/authAPI';
import { Common } from '../index';
function PresistLogin() {
const { data } = useVerifyTokenQuery();
console.log('🚀 ~ file: PresistLogin.jsx:8 ~ PresistLogin ~ data:', data);
return !data ? <Common.LoaderOverlay /> : <Outlet />;
}
export default PresistLogin;
RequiredAuth
import React from 'react';
import { useSelector } from 'react-redux';
import { Navigate, Outlet, useLocation } from 'react-router-dom';
function RequiredAuth() {
const user = useSelector((store) => store.userState.user);
console.log('🚀 ~ file: RequiredAuth.jsx:7 ~ RequiredAuth ~ user:', user, Date.now());
const location = useLocation();
return user ? <Outlet /> : <Navigate to="/login" state={{ from: location }} replace />;
}
export default RequiredAuth;
userSlice
/* eslint-disable no-param-reassign */
import { createSlice } from '@reduxjs/toolkit';
const initialState = {
user: null,
notification: [],
};
export const userSlice = createSlice({
name: 'userSlice',
initialState,
reducers: {
logout: () => initialState,
setUser: (state, action) => {
state.user = action.payload;
},
setNotification: (state, action) => {
state.notification = action.payload;
},
},
});
export const { logout, setUser, setNotification } = userSlice.actions;
export default userSlice.reducer;
await queryFulfilled
will always run a very short moment after your UI rerendered, since that rerender is synchronous and await
only happens on the next tick.
You will have to base your logic around that assumption.