Search code examples
reactjstypescriptauthenticationuse-effect

React - How to check if user is still authenticated



I have a small app in react and I need to check if user is still authenticated after leaving the page. But my solution do not work.
I think I am doing something bad in useEffect part. Because I don't have user on first render.
Is this a good solution or it can be done in better/different way?

Thanks for any suggestions.

Login page - handle login

  const handleLogin = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    try {
      if (!username || !password) {
        setError('Pole musí být vyplněna!');
        return;
      }

      const userData = await axios.post(
        `${process.env.REACT_APP_BACKEND_URL}/account/login`,
        {
          username,
          password,
        }
      );

      if (!userData) {
        throw new Error();
      }

      setUser(userData.data);
      localStorage.setItem('token', userData.data.token);
      navigate(state?.path || '/home');
    } catch (err: any) {
      setError('Nesprávné jméno nebo heslo!');
    }
  };

App.ts

function App() {
  const [user, setUser] = useState<ICurrentUser>({
    name: '',
    role: '',
    username: '',
  });

  useEffect(() => {
    const checkUser = async () => {
      const token = localStorage.getItem('token');
      if (token !== null) {
        const userData = await axios.get(
          `${process.env.REACT_APP_BACKEND_URL}/account/checkToken`,
          {
            headers: {
              Authorization: `Bearer ${token}`,
            },
          }
        );

        setUser({
          name: `${userData.data.firstname} ${userData.data.lastname}`,
          role: userData.data.role,
          username: userData.data.username,
        });
      }
    };

    checkUser().catch((err) => console.log(err));
  }, []);

  return (
    <>
      <UserContext.Provider value={{ user, setUser }}>
        <Routes>
          <Route path="/" element={<Login />}></Route>
          <Route
            path="/home"
            element={
              <ProtectedRoute>
                <HomePage />
              </ProtectedRoute>
            }
          />
          <Route
            path="stazky"
            element={
              <ProtectedRoute>
                <Stazky />
              </ProtectedRoute>
            }
          />
          <Route
            path="zamestnanci"
            element={
              <ProtectedRoute>
                <Zamestnanci />
              </ProtectedRoute>
            }
          />
          <Route
            path="newZam"
            element={
              <ProtectedRoute>
                <NovyZamestnanec action="create" />
              </ProtectedRoute>
            }
          />
          <Route
            path="zam/:id"
            element={
              <ProtectedRoute>
                <NovyZamestnanec action="edit" />
              </ProtectedRoute>
            }
          />
          <Route
            path="new"
            element={
              <ProtectedRoute>
                <NovaStazka />
              </ProtectedRoute>
            }
          />
          <Route
            path="vozidla"
            element={
              <ProtectedRoute>
                <Vozidla />
              </ProtectedRoute>
            }
          />
          <Route
            path="newVehicle"
            element={
              <ProtectedRoute>
                <NoveVozidlo />
              </ProtectedRoute>
            }
          />
          <Route
            path="*"
            element={
              <ProtectedRoute>
                <NotFound />
              </ProtectedRoute>
            }
          />
        </Routes>
      </UserContext.Provider>
    </>
  );
}

ProtectedRoute.ts

const ProtectedRoute: React.FC<IProps> = ({ children }) => {
  const location = useLocation();
  const { user } = useContext(UserContext);
  const isAuth = !!user.username;

  return isAuth ? (
    <Layout>{children}</Layout>
  ) : (
    <Navigate to="/" replace state={{ path: location.pathname }} />
  );
};

export default ProtectedRoute;

Solution

  • I finaly figured out how to do it.

    Only thing needed was add "loading state" to context. I`ll post my code for reference if someone else is strugling with that.

    ProtectedRoute

    const ProtectedRoute: React.FC<IProps> = ({ children }) => {
      const location = useLocation();
      const { user, loading } = useContext(UserContext);
      const isAuth = !!user.username;
    
      if (loading) {
        return <h1>Loading..</h1>;
      }
    
      return isAuth ? (
        <Layout>{children}</Layout>
      ) : (
        <Navigate to="/" replace state={{ path: location.pathname }} />
      );
    };
    

    App.js

    function App() {
      const [user, setUser] = useState<ICurrentUser>({
        name: '',
        role: '',
        username: '',
      });
      const [loading, setLoading] = useState(true);
    
      useEffect(() => {
        const checkUser = async () => {
          const token = localStorage.getItem('token');
          if (token !== null) {
            const userData = await axios.get(
              `${process.env.REACT_APP_BACKEND_URL}/account/checkToken`,
              {
                headers: {
                  Authorization: `Bearer ${token}`,
                },
              }
            );
    
            localStorage.setItem('token', userData.data.token);
            setUser({
              name: `${userData.data.firstname} ${userData.data.lastname}`,
              role: userData.data.role,
              username: userData.data.username,
            });
    
            setLoading(false);
          }
        };
    
        checkUser();
      }, []);
    
      return (
        <>
          <UserContext.Provider value={{ user, setUser, loading }}>
            <Routes>
              <Route path="/" element={<Login />}></Route>
              <Route
                path="/home"
                element={
                  <ProtectedRoute>
                    <HomePage />
                  </ProtectedRoute>
                }
              />
              <Route
                path="stazky"
                element={
                  <ProtectedRoute>
                    <Stazky />
                  </ProtectedRoute>
                }
              />
                       </Routes>
          </UserContext.Provider>
        </>
      );
    }
    

    Context

    interface IContent {
      loading: boolean;
      user: ICurrentUser;
      setUser: (user: ICurrentUser) => void;
    }
    
    export const UserContext = createContext<IContent>({
      user: {
        name: '',
        role: '',
        username: '',
      },
      setUser: () => {},
      loading: true,
    });