skip to Main Content

I traveled through topics similar situation to mine, but I still not found the solution to my case.

When I implemented checking token function on app.js to generate code for Unauthenticated User redirect to Signin page and Authenticated User jump to Hompage.

However, after this part my SPA started getting redirect to home page from any page when refreshing/F5.

Below is my authContext.js:

export const AuthContext = createContext({
  token: null,
  userId: null,
  isLoggedIn: false,
  login: (userId, token) => {},
  logout: () => {},
});

const AuthContextProvider = (props) => {
  const [userId, setUserId] = useState();
  const [token, setToken] = useState();

  const loginUserHandler = useCallback((userId, token) => {
    setUserId(userId);
    setToken(token);
    localStorage.setItem("userData", JSON.stringify({ userId, token }));
  }, []);

  const logoutUserHandler = useCallback(() => {
    setUserId(null);
    setToken(null);
    localStorage.removeItem("userData");
  }, []);

  const initialValues = {
    userId: userId,
    token: token,
    isLoggedIn: !!token,
    login: loginUserHandler,
    logout: logoutUserHandler,
  };

  return <AuthContext.Provider value={initialValues}>{props.children}</AuthContext.Provider>;
};

export default AuthContextProvider;

And app.js code

function App() {
  const { token, login } = useContext(AuthContext);
  let routes;

  useEffect(() => {
    const storedData = JSON.parse(localStorage.getItem("userData"));
    if (storedData && storedData.token) {
      login(storedData.userId, storedData.token);
    }
  }, [login]);

  if (token) {
    routes = (
      <Layout>
        <Switch>
          <Route path="/home" exact>
            <Home />
          </Route>
          <Route path="/leads-manager" exact>
              <Leads />
          </Route>
          <Route path="/phone-manager" exact>
              <Phone />
          </Route>
          <Route path="/new-notifications" exact>
            <Notification />
          </Route>
          <Redirect to="/home" exact />
        </Switch>
      </Layout>
    );
  } else {
    routes = (
      <Switch>
        <Route path="/sign-in" exact>
          <Login />
        </Route>
        <Redirect to="/sign-in" />
      </Switch>
    );
  }

  return <BrowserRouter>{routes}</BrowserRouter>;
}

export default App;

2

Answers


  1. Chosen as BEST ANSWER

    I have changed code a little bit I added one more state to check display the Loader.js (mainly for preventing route redirection to hit unwanted routes)

    First time runs project hasInitVal = false will show the Loader.js. After app.js component mount and useEffect function get executed, value of hasInitVal changed and app.js reload again to display corresponding routes base on the token values.

    autContext.js file

    import { createContext, useCallback, useState } from "react";
    
    export const AuthContext = createContext({
      token: null,
      userId: null,
      isLoggedIn: false,
      login: (userId, token) => {},
      logout: () => {},
    });
    
    const AuthContextProvider = (props) => {
      const [userId, setUserId] = useState(null);
      const [token, setToken] = useState(null);
    
      const loginUserHandler = useCallback((userId, token) => {
        setUserId(userId);
        setToken(token);
        localStorage.setItem("userData", JSON.stringify({ userId, token }));
      }, []);
    
      const logoutUserHandler = useCallback(() => {
        setUserId(null);
        setToken(null);
        localStorage.removeItem("userData");
      }, []);
    
      const initialValues = {
        userId: userId,
        token: token,
        isLoggedIn: !!token,
        login: loginUserHandler,
        logout: logoutUserHandler,
      };
    
      return <AuthContext.Provider value={initialValues}>{props.children}</AuthContext.Provider>;
    };
    
    export default AuthContextProvider;
    

    app.js file

    function App() {
      const { token, login } = useContext(AuthContext);
      const [hasInitVal, setHasInitVal] = useState(false);
    
      useEffect(() => {
        const userToken = JSON.parse(localStorage.getItem("userData"));
        if (userToken && userToken.userId && userToken.token) {
          login(userToken.userId, userToken.token);
        }
        setHasInitVal(true);
      }, [login]);
    
      return (
        <Fragment>
          {!hasInitVal && <Loader />}
          {!token && hasInitVal && (
            <Router>
              <Routes>
                <Route path="/sign-in" element={<Login />} />
                <Route path="*" element={<Navigate to="/sign-in" />} />
              </Routes>
            </Router>
          )}
    
          {token && hasInitVal && (
            <Router>
              <Routes>
                <Route
                  path="/home"
                  element={
                    <Layout>
                      <TblContextProvider>
                        <Home />
                      </TblContextProvider>
                    </Layout>
                  }
                />
    
                <Route
                  path="/leads-manager"
                  element={
                    <Layout>
                      <TblContextProvider>
                        <Leads />
                      </TblContextProvider>
                    </Layout>
                  }
                />
    
                <Route
                  path="/phone-manager"
                  element={
                    <Layout>
                      <TblContextProvider>
                        <Phone />
                      </TblContextProvider>
                    </Layout>
                  }
                />
    
                <Route path="/new-notifications" element={<Notification />} />
                <Route path="*" element={<Navigate to="/home" />} />
              </Routes>
            </Router>
          )}
        </Fragment>
      );
    }
    
    export default App;
    

  2. The issue is that you don’t initialize the token state from localStorage in the AuthContextProvider component when the component mounts. When you reload the page all React state will be lost since it exists only in memory. token is falsey and the App component renders the "unauthenticated" route/redirect. I believe what is happening is the token is falsey and the user is bounced to "/sign-in" and at the same time the useEffect hook in App pulls from local storage and authenticates the user, which updates the userId and token states. token is defined now and the "authenticated" routes render, and because the URL path is still "/sign-in" the Redirect to "/home" is rendered.

    Use useEffect hooks to (A) persist state changes to localStorage and (B) initialize the userId and token states.

    const AuthContextProvider = ({ children }) => {
      const [userId, setUserId] = useState();
      const [token, setToken] = useState();
    
      // Effect to initialize local state and reauthenticate
      useEffect(() => {
        const { userId, token } = JSON.parse(localStorage.getItem("userData")) || {};
        if (userId) setUserId(userId);
        if (token) setToken(token);
    
        if (userId && token) {
          login(userId, token);
        }
      }, []);
    
      useEffect(() => {
        if (token || userId) {
          localStorage.setItem("userData", JSON.stringify({ userId, token }));
        } else {
          localStorage.removeItem("userData");
        }
      }, [token, userId]);
    
      const loginUserHandler = useCallback((userId, token) => {
        setUserId(userId);
        setToken(token);
      }, []);
    
      const logoutUserHandler = useCallback(() => {
        setUserId(null);
        setToken(null);
      }, []);
    
      const initialValues = {
        userId: userId,
        token: token,
        isLoggedIn: !!token,
        login: loginUserHandler,
        logout: logoutUserHandler,
      };
    
      return (
        <AuthContext.Provider value={initialValues}>
          {children}
        </AuthContext.Provider>
      );
    };
    

    Update the App component to explicitly check if the token is undefined when the component mounts to conditionally return early so neither the "authenticated" or "unauthenticated" routes/redirect logic are applied.

    function App() {
      const { token } = useContext(AuthContext);
    
      if (token === undefined) {
        return null; // or loading indicator/spinner/etc
      }
    
      return (
        <BrowserRouter>
          {token
            ? (
              <Layout>
                <Switch>
                  <Route path="/home" component={Home} />
                  <Route path="/leads-manager" component={Leads} />
                  <Route path="/phone-manager" component={Phone} />
                  <Route path="/new-notifications" component={Notification} />
                  <Redirect to="/home" />
                </Switch>
              </Layout>
            )
            : (
              <Switch>
                <Route path="/sign-in" component={Login} />
                <Redirect to="/sign-in" />
              </Switch>
            )
          }
        </BrowserRouter>
      );
    }
    
    export default App;
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search