I have this scenario that is after the user login and assuming it is success, user details / user token is stored to localStorage
and will automatically navigate to dashboard page, dashboard page has some api calls and those api calls required/needs token
that is stored in the localStorage
, my problem is that it is unable to retrieve those values in localStorage
, but when I check from localStorage
using console, the key/value is there, I noticed that, I need to refresh the page to retrieve those details without a problem. How can I possibly fix this issue? to be able to get localStorage
value after navigating to another component?
Here is my code for index.tsx
ReactDOM.render( <AuthContextProvider> <App /> </AuthContextProvider>, document.getElementById("root") );
AuthContext code:
const AuthContext = React.createContext({ user: "", isLoggedIn: false, login: (userdata: any, expirationTime: string) => {}, logout: () => {}, }); export const AuthContextProvider = (props: any) => { const initialUser = localStorage.getItem("user") || ""; const [userData, setUserData] = useState(initialUser); const userIsLoggedIn = !!userData; const logoutHandler = () => { setUserData(""); localStorage.removeItem("user"); }; const loginHandler = async ( user: any, expirationTime: string ) => { localStorage.setItem("user", JSON.stringify(user)); setUserData(user); }; const contextValue = { user: userData, isLoggedIn: userIsLoggedIn, login: loginHandler, logout: logoutHandler, }; return ( <AuthContext.Provider value={contextValue}> {props.children} </AuthContext.Provider> ); }; export default AuthContext;
App.tsx code
function App() { const authCtx = useContext(AuthContext); return ( <BrowserRouter> <Routes> {!authCtx.isLoggedIn && ( <Route element={<LoginLayout />}> <Route index element={<SignInForm />} /> {/*Other links here */} </Route> )} {authCtx.isLoggedIn && ( <Route element={<AdminLayout />}> <Route path="dashboard" element={<DashboardScreen />} /> {/*Other links here */} </Route> )} <Route path="*" element={<PageNotFound />} /> </Routes> </BrowserRouter> ); }
Login code:
try { await AuthService.login(email, password).then( (res) => { authCtx.login(res, "0"); navigate("../dashboard", { replace: true }); }, (error) => { } ); } catch (err) { console.log(err); }
Dashboard code:
const loadCountOnlineUsers = useCallback(async () => { try { await DashboardService.loadCountOnlineUsers().then( (res) => { setCntOnlineUsers(res.count); setflagOnlineUsers(false); }, (error) => { setflagOnlineUsers(false); } ); } catch (err) { console.log(err); setflagOnlineUsers(false); } }, [setCntOnlineUsers, setflagOnlineUsers]); useEffect(() => { loadCountOnlineUsers(); }, [loadCountOnlineUsers]);
Dashboard service code:
const config = { headers: { "Content-Type": "application/json", Authorization: AuthHeader(), }, params: {}, }; const loadCountOnlineUsers = () => { config["params"] = {}; return axios .get(API_URL + "api/v1/dashboard-related/online-users", config) .then((response) => { return response.data; }); }; const DashboardService = { loadCountOnlineUsers, }; export default DashboardService;
Auth-header code:
export default function AuthHeader() { const user = JSON.parse(localStorage.getItem("user") || "{}"); if (user && user.token) { return "Bearer " + user.token; } else { return ""; } }
Advertisement
Answer
The problem is that the check to localStorage in AuthHeader() isn’t updating reactively. The fix would be to rewrite AuthHeader to accept the user data like this:
export default function AuthHeader(user) { const user = JSON.parse(user || "{}"); if (user && user.token) { return "Bearer " + user.token; } else { return ""; } }
and then continue the data piping into the area where AuthHeader() is called, perhaps like this:
const config = (user) => ({ headers: { "Content-Type": "application/json", Authorization: AuthHeader(), }, params: {}, }); const loadCountOnlineUsers = (user) => { config["params"] = {}; return axios .get(API_URL + "api/v1/dashboard-related/online-users", config(user)) .then((response) => { return response.data; }); }; const DashboardService = { loadCountOnlineUsers, };
Lastly, using an effect in the dashboard to update it reactively, while connecting to context:
const authCtx = useContext(AuthContext); const user = authCtx.user; const loadCountOnlineUsers = (user) => { return useCallback(async () => { try { await DashboardService.loadCountOnlineUsers(user).then( (res) => { setCntOnlineUsers(res.count); setflagOnlineUsers(false); }, (error) => { setflagOnlineUsers(false); } ); } catch (err) { console.log(err); setflagOnlineUsers(false); } }, [setCntOnlineUsers, setflagOnlineUsers]); } useEffect(() => { loadCountOnlineUsers(user); }, [loadCountOnlineUsers, user]);