I try to do RequierAuth lock to protect against an unauthorized user. First created the context where auth
should accept the token
:
AuthProvider.js:
const AuthContext = createContext({});
export const AuthProvider = ({ children }) => {
const [auth, setAuth] = useState({});
return (
<AuthContext.Provider value={{ auth, setAuth }}>
{children}
</AuthContext.Provider>
)
}
export default AuthContext;
Next created a hook for the above context:
useAuth.js
const useAuth = () => {
return useContext(AuthContext);
}
export default useAuth;
Next, the actual "lock" for protection, where I check if there is a token and return either the page or send the user to the login page:
RequierAuth.js
const RequierAuth = () => {
const {auth} = useAuth();
const location = useLocation();
return (
auth.token?<Outlet/> : <Navigate to = "/auth" state = {{from:location}} replace />
);
}
export default RequierAuth;
App.js
function App() {
return (
<>
...
<div className="App">
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route path="auth" element={<Login />} />
<Route path="reg" element={<Register />} />
<Route element = {<RequierAuth/>}>
<Route path="home" element={<Home />} />
</Route>
</Route>
</Routes>
</BrowserRouter>
</div>
</>
);
}
export default App;
index.js
root.render(
<React.StrictMode>
<AuthProvider>
<App />
</AuthProvider>
</React.StrictMode>
);
And actually the question is that now when I call setAuth
on the login page:
LoginForm.js
const Login = () =>{
const {auth,setAuth} = useAuth();
const [authResponce,setAuthResponce] = useState(null);
const [login,setLogin] = useState("");
const onChangeLogin = (e) => {
e.preventDefault();
const username = e.target.value;
setLogin(username);
};
const [password,setPassword] = useState("");
const onChangePassword = (e) => {
const password = e.target.value;
setPassword(password);
};
const instance = axios.create({
baseURL: "http://localhost:8080",
headers: {
"Content-Type": "application/json",
},
});
const postUser = async (user) =>{
return instance.post("http://localhost:8080/auth", user);
}
const onLogin = (e) =>{
e.preventDefault();
const user = {
login: login,
password: password,
};
(async() => {
const response = await postUser(user);
const data = response.data;
console.log(data);
const token = data.token;
console.log(token);
setAuth({token});
console.log(auth);
console.log(auth.token);
})();
};
return (
<div className="Auth-form-container">
<form className="Auth-form" onSubmit={onLogin}>
<div className="Auth-form-content">
<h3 className="Auth-form-title">Sign In</h3>
<div className="form-group mt-3">
<label>Login</label>
<input
type="login"
className="form-control mt-1"
placeholder="Enter login"
value={login}
onChange={onChangeLogin}
/>
</div>
<div className="form-group mt-3">
<label>Password</label>
<input
type="password"
className="form-control mt-1"
placeholder="Enter password"
value={password}
onChange={onChangePassword}
/>
</div>
<div className="d-grid gap-2 mt-3">
<button type="submit" className="btn btn-primary">
Submit
</button>
</div>
</div>
</form>
</div>
)
};
export default Login;
First, if you get a token
for the first time, then why is the value not printed in the console, if you get it again on the form, it already prints the expected value, but when you switch to home
, it sends it back to the login.
I set breakpoint and at the moment of checking auth.token?
indicates that the value is not set, although setAuth
has set the value.
The check itself seems to work, if you put the default value in auth and try to compare with it, then we will normally get to /home
.
I've only recently started studying and I can't figure out what the error is, so I'll be glad for help to figure it out.
CodePudding user response:
Issue
First, updating a state
is an asynchronous
tasks. A re-render is needed in order to have the updated value. Which is why you are not seeing change whith those lines that you have inside onLogin
:
setAuth({token});
console.log(auth);
Second, after the login process, you said in the comments that you are using the browser to redirect to /home
. Well you should know that, doing so refreshes the page, so all your states come to their initial values, so auth
would be {}
. This is why it's redirecting to "/auth"
.
Solution
You should use React Router Dom's redirection mechanism, useNavigate
for example. Change LoginForm.js
slightly, like so (I added comments in the code):
import { useEffect } from "react"; // line to add
import { useNavigate } from "react-router-dom"; // line to add
const Login = () => {
const navigate = useNavigate(); // line to add
const { auth, setAuth } = useAuth();
const [authResponce, setAuthResponce] = useState(null);
const [login, setLogin] = useState("");
const onChangeLogin = (e) => {
e.preventDefault();
const username = e.target.value;
setLogin(username);
};
const [password, setPassword] = useState("");
const onChangePassword = (e) => {
const password = e.target.value;
setPassword(password);
};
const instance = axios.create({
baseURL: "http://localhost:8080",
headers: {
"Content-Type": "application/json",
},
});
const postUser = async (user) => {
return instance.post("http://localhost:8080/auth", user);
};
// useEffect to add
useEffect(() => {
if (auth) {
console.log(auth); // add your logs here to see the updates after re-render
navigate("/home");
}
}, [auth]);
const onLogin = (e) => {
e.preventDefault();
const user = {
login: login,
password: password,
};
(async () => {
const response = await postUser(user);
const data = response.data;
console.log(data);
const token = data.token;
console.log(token);
setAuth({ token });
})();
};
return (
<div className="Auth-form-container">
<form className="Auth-form" onSubmit={onLogin}>
<div className="Auth-form-content">
<h3 className="Auth-form-title">Sign In</h3>
<div className="form-group mt-3">
<label>Login</label>
<input
type="login"
className="form-control mt-1"
placeholder="Enter login"
value={login}
onChange={onChangeLogin}
/>
</div>
<div className="form-group mt-3">
<label>Password</label>
<input
type="password"
className="form-control mt-1"
placeholder="Enter password"
value={password}
onChange={onChangePassword}
/>
</div>
<div className="d-grid gap-2 mt-3">
<button type="submit" className="btn btn-primary">
Submit
</button>
</div>
</div>
</form>
</div>
);
};
export default Login;
Improvement
The above solution would work, but if you refresh the page manually, there is no way to remember that a user has been logged in. If you want that feature, you should use something like localStorage
. For that, change that useEffect
I added inside LoginForm.js
to:
useEffect(() => {
if (auth) {
console.log(auth); // add your logs here to see the updates after re-render
localStorage.setItem("token", auth.token); // so you get it later
navigate("/home");
}
}, [auth]);
Change AuthProvider.js
so you get the token form localStorage
if there is one:
const AuthContext = createContext({});
export const AuthProvider = ({ children }) => {
const [auth, setAuth] = useState({ token: localStorage.getItem("token") });
return <AuthContext.Provider value={{ auth, setAuth }}>{children}</AuthContext.Provider>;
};
export default AuthContext;