1

I am working on login/register components in React, and I'm using useContext and useReducer hooks to manage state. This is the first time I've tried it this way, and I'm not sure why the state is not changing. Below are the files for the login component. I've shown where I've console logged and what the results are.

This is the api:

export const login = ({ email, password }) => {

  console.log(email, password); 
  // jennifer@jennifer.com 12345678

  return fetch(`${DEV_AUTH_URL}/signin`, {
    method: 'POST',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      email,
      password,
    }),
  })
    .then((res) => {
      return res.ok
        ? res.json()
        : res.json().then(err => PromiseRejectionEvent.reject(err));
    })
    .then(data => data);
};

This is the state manager:

const AuthState = (props) => {
  const initialState = {
    token: null,
    isAuth: false,
    errorMsg: null,
    user: {},
  };

  const [state, dispatch] = useReducer(AuthReducer, initialState);
  const history = useHistory();

  const handleLogin = (formData) => {
    login(formData)
      .then((res) => {

        console.log(res); 
        // {token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7I…zk3fQ.Qx9zDeXBecToIEScCTDXzkBiTnATHab4cnyg0aSMdLE"}


        res && res.token
          ? dispatch({ type: LOGIN_SUCCESS, payload: res })
          : dispatch({ type: LOGIN_FAIL, payload: res });
      })
      .then(() => {
        closeAllPopups();

        console.log('jwt: ', localStorage.getItem('jwt'));
        // {token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7I…zk3fQ.Qx9zDeXBecToIEScCTDXzkBiTnATHab4cnyg0aSMdLE"}

        console.log('token: ', state.token);
        // token:  null
        console.log('isAuth: ', state.isAuth);
        // isAuth: false

        history.push('/');
      })
      .catch((err) => dispatch({ type: LOGIN_FAIL, payload: err.toString() }));
  };

This is what is in the reducer:

import {
  LOGIN_SUCCESS,
  LOGIN_FAIL,
} from '../types';

export default (state, action) => {
  switch (action.type) {
    case LOGIN_SUCCESS:
      localStorage.setItem('jwt', action.payload.token);
      return {
        ...state,
        token: action.payload.token, // this is not changing state
        isAuth: true, // this is also not changing state
      };
    case LOGIN_FAIL:
      return {
        ...state,
        token: null,
        user: {},
        isAuth: false,
        errorMsg: action.payload,
      };
    default:
      return state;
  }
};
maria05
  • 13
  • 1
  • 5

1 Answers1

1

Issue

It's a bit unclear what you really want the logic flow to be, but React state updates are asynchronous and the state from the render cycle the handleLogin callback is invoked in is enclosed in callback scope for the life of that function. Just because React state updates are asynchronous doesn't mean they can be awaited on either.

Solution

From what I can tell you want to call login, and upon login success dispatch an action, close popups, log some values, and navigate home. Other than logging updated state this can all be completed in the first thenable.

const handleLogin = (formData) => {
  login(formData)
    .then((res) => {
      console.log(res); 

      res?.token
        ? dispatch({ type: LOGIN_SUCCESS, payload: res })
        : dispatch({ type: LOGIN_FAIL, payload: res });

      closeAllPopups();
      history.push('/');
    })
    .catch((err) => {
      dispatch({ type: LOGIN_FAIL, payload: err.toString() });
    });
};

Use an useEffect hook to log any state updates.

useEffect(() => {
  console.log('jwt: ', localStorage.getItem('jwt'));
  console.log('token: ', state.token);
  console.log('isAuth: ', state.isAuth);
}, [state]);
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • What I found from logging the state updates in useEffect is that they are, in fact, getting updated correctly by the reducer, but they are updating twice more and the settings go back to initial state in the last render. So now I better know how to troubleshoot. I don't fully understand asynchronous code and the order it will execute, so it's always a bit of trial and error (hence the number of console.logs). Thank you! – maria05 May 28 '21 at 23:54
  • 1
    Turns out the `history.push` was causing an error, and the `LOGIN_FAIL` action was putting everything back to the initial state. I've already used your advice about checking state in `useEffect` to debug another component. Thanks! – maria05 May 29 '21 at 03:44