I’m using React to create a login page which after logging in should keep track if the user is logged in. I figured using the react context and state hook would be easier than using something as big and complex as Redux.
I created a login function which works, and after a successfull login it should update my state in my context. The login works (i get a status 200) but my state is not updated in my context.
My ‘AuthContext.jsx’ looks like this
import React, { createContext, useState } from "react";
import { login, register } from "../API/apiMethods";
export const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [authenticated, setAuthenticated] = useState(false);
const authSetter = (state) => {
setAuthenticated(state);
};
const authGetter = () => {
return authenticated;
};
return (
<AuthContext.Provider
value={{
authSetter,
authGetter,
login,
register,
}}
>
{children}
</AuthContext.Provider>
);
};
My login function looks like this
/**
* @description Handles login
* @param {String} email
* @param {String} password
* @returns {Boolean}
*/
export const login = async(email, password) => {
try {
let authenticated = false;
await fetch(BASE_URL + "login", {
method: "POST",
credentials: "include",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
email,
password
}),
}).then((res) => {
authenticated = res.status === 200 ? true : false;
});
return authenticated;
} catch (err) {
console.log(err);
return false;
}
};
And in my login form i try to update the authentication boolean after a successfull login
const {
login,
authSetter,
authGetter
} = useContext(AuthContext);
const submit = async(e) => {
e.preventDefault();
await login(email, password)
.then((authSuccess) => {
if (authSuccess) {
console.log("login successfull");
authSetter(true);
}
})
.then(() => console.log(authGetter()));
};
With this code i expected the console output to be a printed string with ‘login successfull’ and a printed boolean true.
But it seems my state was not updated even though i did call the setter.
I don’t know why it won’t update, can anyone help me?
>Solution :
This piece of code does exactly what it is supposed to. When you update the state it does not happen immediately.
const submit = async(e) => {
e.preventDefault();
await login(email, password)
.then((authSuccess) => {
if (authSuccess) {
console.log("login successfull");
authSetter(true);
}
})
.then(() => console.log(authGetter()));
};
When you call the authSetter(true);, the state update is queued and once the then callback completes it goes to the next then in the chain which has your authGetter(). Now the state update does not happen immediately as I explained, it is queued. So when the last then callback is executed the state update which is queued has not happened and you still see false which is the old value.
You can refactor your AuthProvider in the following way, there is no need to wrap the setter in a function as it would create a new instance of the function when the state is updated (useState on the other hand returns a memoized value of the setter function) and you can simply return the authenticated state without the getter which again has the same issue.
import React, { createContext, useState } from "react";
import { login, register } from "../API/apiMethods";
export const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [authenticated, setAuthenticated] = useState(false);
return (
<AuthContext.Provider
value={{
setAuthenticated,
authenticated,
login,
register,
}}
>
{children}
</AuthContext.Provider>
);
};
In your form, you can have an extra useEffect to check whether you have logged in successfully. the useEffect will run when the authenticated state has been updated.
const {
login,
setAuthenticated,
authenticated
} = useContext(AuthContext);
const submit = async(e) => {
e.preventDefault();
const authSuccess = await login(email, password);
if (authSuccess) {
console.log("login successfull");
authSetter(true);
}
};
useEffect(() => {
console.log(authenticated);
}, [authenticated]);
