Skip to content

ReactJS: Unable to retrieve properly localStorageItem after navigate to another component

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 "";
    }
  }

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]);