Search code examples
javascriptreactjsreact-router-dom

React.js, Auth Component does not redirect properly


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 is 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.


Solution

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