I'm trying to sign a user in, and update my global context with the user data. To keep the user signed in I'm storing their data in local storage.
I'm using react-hooks to take care of the state, hence I have defined a state: let [userData, setUserData] = useState({});
.
Since I wan't to keep the user signed in I store their data in local storage during sign in. This works and the data does in fact get stored in local storage.
My problem is however that I can't set the initial userData
state equal to the current data from local storage. In other words the userData
state gets reset to default on reload.
I thought that getting the initial data from local storage and assigning it to state inside the useEffect
hook would work. But the state does not update when calling setUserData
inside useEffect
.
AuthContext.js:
import React, { createContext, useState, useEffect } from 'react';
export const AuthContext = createContext();
const AuthContextProvider = props => {
let [userData, setUserData] = useState({});
const loginUser = (data) => {
localStorage.setItem('userData', JSON.stringify({
key: data.key,
id: data.id,
email: data.email,
first_name: data.first_name,
last_name: data.last_name
})); // Save the user object in local storage
setUserData({
key: data.key,
id: data.id,
email: data.email,
first_name: data.first_name,
last_name: data.last_name
}); // Set user data
};
const logoutUser = () => {
localStorage.removeItem('userData');
setUserData({}); // Empty user data state
newToast('Successfully signed out');
};
useEffect(() => {
const localUser = JSON.parse(localStorage.getItem('userData'));
if (localUser && localUser.key) {
setUserData({
key: localUser.key,
id: localUser.id,
email: localUser.email,
first_name: localUser.first_name,
last_name: localUser.last_name
}); // Set user data
}
}, [])
return (
<AuthContext.Provider value={{ userData, loginUser, logoutUser, newToast }}>
{props.children}
</AuthContext.Provider>
)
}
export default AuthContextProvider;
Signin.js:
const Signin = props => {
let [loading, setLoading] = useState(false);
let [formError, setFormError] = useState(false);
const { userData, loginUser, newToast } = useContext(AuthContext);
const { register, handleSubmit, errors, setError, clearError } = useForm();
const onSubmit = e => {
setLoading(true);
setFormError(false);
clearError(); // Clear all erros on form
axios
.post('users/auth/login/', {
headers: { 'Content-Type': 'application/json' },
email: `${e.email}`,
password: `${e.password}`,
})
.then(res => {
const { data } = res
loginUser(data);
newToast('Successfully signed in');
})
.catch((error) => {
const { data } = error.response;
console.log(data);
data.email && setError("email", "", data.email);
data.password1 && setError("password", "", data.password1);
setFormError(true);
})
setLoading(false);
};
return ( ... );
}
Updated answer (Aug. 15, 2022):
Since accessing the local storage on every render is expensive, it is preferred to only access it during the initial render (see Wayne Ellery's comment).
So quoting Erol's solution:
const [user, setUser] = useState([], () => {
const localData = localStorage.getItem('userData');
return localData ? JSON.parse(localData) : [];
});
Original answer:
So I figured out a solution!
In AuthContext.js i didn't need to assign the state in useEffect
.
Instead I get the initial data directly when defining the state hooks:
const localUser = JSON.parse(localStorage.getItem('userData')) || {};
let [userData, setUserData] = useState(localUser);
That way I don't need the useEffect
hook at all.
I hope this is the recommended way of doing it.