Search code examples
javascriptreactjsreact-router-domreact-context

Require Auth not getting context value


I am trying to send the user to login page for some special pages if he is not logged in. I am using react-router. Here is my RequireAuth component:

const RequireAuth = ({ children }) => {
  const { email, loading } = useContext(LoginRegisterContext);
  console.log(email);
  console.log(children.props);

  let location = useLocation();
  if (loading) {
    return <div>Loading...</div>;
  }
  if (!email) {
    return <Navigate to="/loginregister" state={{ from: location }} replace />;
  } else return children;
};

I have created this context to share the values among the components:

export const LoginRegisterContext = createContext({});

export function LoginRegisterContextProvider({ children }) {
  // eslint-disable-next-line react-hooks/rules-of-hooks
  const [email, setEmail] = useState(null);
  const [password, setPassword] = useState(null);
  const [id, setId] = useState(null);
  const [loading, setLoading] = useState(true);
  console.log(id, email);

  useEffect(() => {
    const fetchOwner = async () => {
      await axios
        .get("/api/v1/user/ownerprofile", { withCredentials: true })
        .then((res) => {
          setEmail(res.data.email);
          setId(res.data._id);
        })
        .catch((err) => console.log(err))
        .finally(setLoading(false));
    };
    fetchOwner();
  }, []);

  return (
    <LoginRegisterContext.Provider
      value={{ setEmail, setPassword, setId, id, email, loading }}
    >
      {children}
    </LoginRegisterContext.Provider>
  );
}

Lastly this is my App.js routes to protect the pages with RequireAuth components. Still the email is 'null'. But the loading variable gets false on console. Means I am getting the loading variable but not the email.

<Route
  path="/ownerprofile"
  element={
    <LoginRegisterContextProvider>
      <RequireAuth>
        <OwnerProfile></OwnerProfile>
      </RequireAuth>
    </LoginRegisterContextProvider>
  }
></Route>

My loginRegister page code:

const LoginRegister = () => {
  const location = useLocation();
  const navigate = useNavigate();
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [status, setStatus] = useState("login");

  const {
    setEmail: setLoggedEmail,
    setId,
    id,
    email: loggedEmail,
  } = useContext(LoginRegisterContext);

  let from = location.state?.from?.pathname || "";

  const handleEmail = (ev) => {
    setEmail(ev.target.value);
  };

  const handlePass = (ev) => {
    setPassword(ev.target.value);
    console.log(password);
  };

  const handleSubmit = async (event) => {
    event.preventDefault();

    await axios
      .post(
        status === "register" ? "api/v1/user/register" : "api/v1/user/login",
        {
          email: email,
          password: password,
        },
        { withCredentials: true }
      )
      .then((res) => {
        setLoggedEmail(res.data.email);
        setId(res.data.id);
      })
      .catch((err) => console.log(err));
    navigate(from, { replace: true });
  };

  console.log(loggedEmail);

  if (loggedEmail) {
    navigate(from, { replace: true });
  }

Solution

  • You've at least a couple issues in the code, in the useEffect hook in the provider.

    1. The useEffect hook incorrectly includes email in its dependency array when email is the state the effect updates. It should be removed.
    2. The finally block is immediately invoking the setLoading state. This means the context isn't able to correctly provide the loading state value to the context consumers. finally should be passed a callback function.

    The useEffect hook also had a useless snippet of code that checked if the email was truthy and just enqueued a state update to the current value. This also should be removed.

    An additional note, it's generally considered an anti-pattern to mix async/await and Promise chains. Select one or the other. Here's I've kept the Promise chain you were using and removed the async/await since it wasn't doing anything for you.

    Example:

    useEffect(() => {
      const fetchOwner = () => {
        axios
          .get("/api/v1/user/ownerprofile", { withCredentials: true })
          .then((res) => {
            setEmail(res.data.email);
            setId(res.data._id);
          })
          .catch((err) => console.log(err))
          .finally(() => setLoading(false)); // <-- callback
      };
    
      fetchOwner();
    }, []); // <-- empty dependency
    

    Edit require-auth-not-getting-context-value