Search code examples
reactjsreduxreact-reduxreact-routerrtk-query

RTK query API not updating state immidiately


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;

Solution

  • 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.