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 });
}
You've at least a couple issues in the code, in the useEffect
hook in the provider.
useEffect
hook incorrectly includes email
in its dependency array when email
is the state the effect updates. It should be removed.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