Search code examples
reactjsauthenticationlocal-storagetokenreact-hooks

Assign local storage to react state. React Hooks


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 ( ... );
}

Solution

  • 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.