React context returns initial state every time using it - despite setState

Viewed 1172

I have created this simple authentication component. When app is refreshed and ContextProvider is loaded useEffect method is fired and sets user logged in.

Have a look at code

import React, { createContext, useState, useContext, useEffect } from 'react';
import User from 'utils/User/User';
import useLocale from 'store/LocaleContext';
import { tokenKey } from 'consts';

const AuthContext = createContext();

const initialState = { auth: false, user: null };

export const AuthContextProvider = ({ children }) => {
  const [state, setState] = useState(initialState);
  const { setLanguage } = useLocale();

  const loginUser = user => {
    setLanguage(user.getLanguage());
    user.login();
    setState({ ...state, user: user, auth: true });

    setTimeout(() => {
      logOutUser();
    }, 2000);
  };

  const logOutUser = () => {
    console.log(state.user, state); // Output: null, {auth: false, user: null}
    state.user.logout();
    setState(initialState);
  };

  useEffect(() => {
    User.Credentials.loginWithToken(localStorage.getItem(tokenKey))
      .then(data => data.data)
      .then(data => {
        if (data.success && data.user) {
          loginUser(new User(data.user));
        }
      })
      .catch(err => {
        console.log(err);
        logOutUser();
      });
  }, []);

  return (
    <AuthContext.Provider
      value={{
        auth: state.auth,
        user: state.user,
        loginUser,
        logout: logOutUser
      }}
    >
      {children}
    </AuthContext.Provider>
  );
};

export default () => useContext(AuthContext);

loginUser method executes correctly but then I want to test logoutUser function. So I decided to run logoutUser function after 2 second of logging in, but it reads old state.

When I log state and state.user I get output: null, {auth: false, user: null}. So as you can see it reads initialState or state is not changing.

Can you tell me what's problem and what I'm doing wrong?

Any help will be apreciated

1 Answers

Two things. Both related to useState being asynchronous.

  1. You need to use a function as the first parameter passed to setState. React will call it with the at-call-time-current state and expect you to return an Object to merge into state.

setState(state => ([...state, user: user, auth: true]));

  1. You need to use the Hook useEffect() to make sure you are getting the updated version of state when you console.log().

useEffect(() => { console.log(state) }, [state]);

Let me know if this helps!

Related