I am trying to "copy" my real world project with CodeSandbox (here). I have a login form and when login, it sets a cookie with auth data. When the <LoginPage>
component mounts, it should check for that cookie and redirect back to <HomePage>
component if that exists.
In my case that doesn't work as expected since it waits until I write something in the <LoginPage>
form.
According to the hookrouter documentation navigate()
should work as react-router-dom <Redirect />
component.
So, this is the project src
folder structure:
App
: src/index.jsimport React, { useEffect } from "react";
import ReactDOM from "react-dom";
import { useRoutes, A, navigate } from "hookrouter";
import { useCookies } from "react-cookie";
import HomePage from "./components/pages/Home";
import LoginPage from "./components/pages/Login";
import AuthContextWrapper from "./context/Auth";
const Logout = () => {
const [cookies, setCookie, removeCookie] = useCookies(["token"]);
useEffect(() => {
console.log("Logout triggered");
removeCookie("token");
navigate("/", true);
}, []);
return <p>Closing session</p>;
};
const routes = {
"/": () => <HomePage />,
"/login": () => <LoginPage />,
"/logout": () => <Logout />
};
const App = () => {
const Router = useRoutes(routes);
return Router ? (
<AuthContextWrapper>{Router}</AuthContextWrapper>
) : (
<h1>
Error 404: <A href="/">Go back</A>
</h1>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<HomePage />
: src/components/pages/Home/index.jsimport React, { Fragment, useContext } from "react";
import { A } from "hookrouter";
import AuthContext from "../../../context/Auth/auth";
const HomePage = () => {
const context = useContext(AuthContext);
console.log("Home context", context);
return (
<Fragment>
<A href="/login">Login page</A>
{context.token.length ? <A href="/logout">Logout</A> : ""}
</Fragment>
);
};
export default HomePage;
<LoginPage />
: src/components/pages/Login/index.jsimport React, { useState, useContext, useEffect } from "react";
import AuthContext from "../../../context/Auth/auth";
import { navigate } from "hookrouter";
const LoginPage = () => {
const context = useContext(AuthContext);
const [emailState, setEmailState] = useState("");
const [passwordState, setPasswordState] = useState("");
const [statusState, setStatusState] = useState({
color: "black",
status: ""
});
useEffect(() => {
console.log("Login context:", context);
if (context.token) {
navigate("/");
}
}, []);
return (
<form onSubmit={handleSubmit}>
<input
value={emailState}
onChange={e => setEmailState(e.target.value)}
placeholder="Email"
/>
<input
value={passwordState}
onChange={e => setPasswordState(e.target.value)}
placeholder="Password"
type="password"
/>
<p style={{ color: statusState.color }}>{statusState.status}</p>
<button>Login</button>
</form>
);
async function handleSubmit(e) {
e.preventDefault();
setStatusState({
color: "blue",
status: "Validating"
});
await context.login(emailState, passwordState);
if (context.error) {
setStatusState({
color: "red",
status: context.error
});
}
if (context.token) {
navigate("/");
}
}
};
export default LoginPage;
<AuthContextWrapper />
: src/context/Auth/index.jsimport React, { useEffect } from "react";
import Context from "./auth";
import { useCookies } from "react-cookie";
const AuthContextWrapper = props => {
const [cookies, setCookie] = useCookies(["token"]);
const defaultValue = {
token: "",
error: "",
loading: false,
login: async (email, password) => {
setTimeout(() => {
defaultValue.loading = true;
if (password !== "123") {
defaultValue.error = "Wrong credentials";
} else {
defaultValue.error = "";
defaultValue.token = "jwtencodedtoken$123";
setCookie("token", defaultValue.token);
}
}, 1000);
defaultValue.loading = false;
}
};
useEffect(() => {
if (cookies.token) {
defaultValue.token = cookies.token;
}
});
return (
<Context.Provider value={defaultValue}>{props.children}</Context.Provider>
);
};
export default AuthContextWrapper;
So, I guess my head just exploded... This is a live example living inside CodeSandbox. Something wrong with hooks maybe (I am still learning them).
Why this does not work? What am I doing wrong? Any comments are appreciated.
Okay so I updated your code sandbox here.
src/context/Auth/index.js
Your context provider was a bit awkward. Your going to have a bad time if you try manipulating an object directly like that with your login function. If you want to see updates, bind them to state and pass them in as values to your provider.
src/components/pages/Login/index.js
Not necessary for that handleSubmit function to be async, also how you set this up can be problematic without making the login function a promise. You end up returning early on your await instead of after the timeout.
You are better off reacting to those context updates inside the useEffect
hook with proper dependencies. This way you can make necessary updates in this component whenever the context updates.