Home > Software design >  React.js, Auth Component does not redirect properly
React.js, Auth Component does not redirect properly

Time:08-22

I have created this Auth Component and it works fine. Except that, It does not redirect if the unauthenticated user tries to visit /dashboard.

The backend upon receiving /api/me request, knows the user by having the cookie. So I have (Cookie-Session) Authentication technique.

export const UserContext = createContext();

const Auth = ({ children }) => {
  const [user, setUser] = useState(null);
  const [gotUser, setGotUser] = useState(false);

  const navigate = useNavigate();

  const getUser = async () => {
    const res = await fetch('/api/me');
    const data = await res.json();
    setUser(data);
    if (user) {
      setGotUser(true);
    }
  };

  useEffect(() => {
    if (!gotUser) {
      getUser();
    }
  }, [user, gotUser, navigate]);

  if (!user) {
    navigate('/login');
  }
  console.log(user);

  return <UserContext.Provider value={user}>{children}</UserContext.Provider>;
};

So the main issue is that no redirection done. Also, The user passed to the context is not updated properly. Maybe because I am confused about what to use in useEffect .

Any help is appreciated.

CodePudding user response:

Issues

There are a couple issues:

  1. The "unauthenticated" state matches the "I don't know yet" state (i.e. the initial state value) and the component is redirecting too early. It should wait until the user state is confirmed.
  2. The navigate function is called as an unintentional side-effect directly in the body of the component. Either move the navigate call into a useEffect hook or render the Navigate component to issue the imperative navigation action.

Solution

Use an undefined initial user state and explicitly check that prior to issuing navigation action or rendering the UserContext.Provider component.

const Auth = ({ children }) => {
  const [user, setUser] = useState(); // <-- initially undefined

  const navigate = useNavigate();

  const getUser = async () => {
    try {
      const res = await fetch('/api/me');
      const data = await res.json();
      setUser(data); // <-- ensure defined, i.e. user object or null value
    } catch (error) {
      // handler error, set error state, etc...
      setUser(null); // <-- set to null for no user
    }
  };

  useEffect(() => {
    if (user === undefined) {
      getUser();
    }
  }, [user]);

  if (user === undefined) {
    return null; // <-- or loading indicator, spinner, etc
  }

  // No either redirect to log user in or render context provider and app
  return user
    ? <Navigate to="/login" replace />
    : <UserContext.Provider value={user}>{children}</UserContext.Provider>;
};

CodePudding user response:

useEffect runs after your JSX is rendered, so as your code is made, on a page refresh this if (!user) that calls navigate('/login') will always pass, as before the useEffect does its work, user is null, that inital value you gave to useState. Yet it's not redirecting because navigate does not work inside JSX, it should replaced with Navigate the component.

Also, in getUser, you have this if (user) juste after setUser(data), that wouldn't work well as user won't getupdated immediately, as updating a state is an asynchronous task which takes effect after a re-redner .

To fix your problems you can add a checking state, return some loader while the user is being verified. Also you can optimise a little bit your code overall, like getting ride of that gotUser state:

export const UserContext = createContext();

const Auth = ({ children }) => {
  const [user, setUser] = useState(null);
  const [checking, setChecking] = useState(true);

  const getUser = async () => {
    try {
      const res = await fetch("/api/me");
      const data = await res.json();
      setUser(data);
    } catch (error) {
      setUser(null);
    } finally {
      setChecking(false);
    }
  };

  useEffect(() => {
    if (!user) {
      getUser();
    }
  }, [user]);

  if (checking) {
    return <p>Checking...</p>;
  }

  if (!user) {
    <Navigate to="/login" replace />
  }

  return <UserContext.Provider value={user}>{children}</UserContext.Provider>;
};

export default Auth;
  • Related