Home > Mobile >  React context api with useReducer state not changing
React context api with useReducer state not changing

Time:12-31

I have an Auth screen and a HomeScreen, and to manage the state I am using Context API in which I have 2 states -> 1) user state on which I'm deciding whether I am authenticated or not (while fetching info from the server), 2) loading state to show a loading screen when all this is happening. The problem is, everything is working, even setting the state of user from null to a well defined object coming from my API, and in the same way I'm changing my loading state but it is not changing, it is false everytime despite I'm dispatching using the same method.

Context file:

import React, { createContext, useContext, useReducer } from 'react';
import {UserReducer} from './reducers';

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

const UserContext = createContext(initialState);

export const UserProvider = ({children}) => {
  const [globalState, dispatch] = useReducer(UserReducer, initialState);

  return (
    <UserContext.Provider value={{
      user: globalState.user,
      loading: globalState.loading,
      dispatch,
    }} >
      {children}
    </UserContext.Provider>
  );
};

export default function() {
  return useContext(UserContext);
}

Actions:

export const SET_USER_DETAILS = (userDetails) => {
  return {
      type: 'SET_USER_DETAILS',
      payload: userDetails,
  };
};

export const SET_LOADING = (loadingState) => {
  return {
    type: 'SET_LOADING',
    payload: loadingState,
  };
};

Reducer:

export const UserReducer = (state, action) => {
  switch (action.type) {
    case 'SET_USER_DETAILS':
      return {
        ...state,
        user: action.payload,
      };
    case 'SET_LOADING':
      return {
        ...state,
        loading: action.payload,
      };
    default:
      return state;
  }
};

Main Navigation File:

const Navigation = () => {
  const {user, dispatch, loading} = useAuth();
  console.log(user); // after successful fetch from api, I'm getting the desired user data.
  console.log(loading); //  PROBLEM => always false.

  useEffect(() => {
    checkUserLoggedInStatus(dispatch);
  }, [dispatch]);

  return loading ? (
    <LoadingScreen />
  ) : !user ? (
    <Stack.Navigator screenOptions={{headerShown: false}}>
      <Stack.Screen name="Login" component={LoginScreen} />
      <Stack.Screen name="Register" component={RegisterScreen} />
      <Stack.Screen name="Verify" component={OTPVerificationScreen} />
    </Stack.Navigator>
  ) : (
    <Stack.Navigator>
      <Stack.Screen name="Home" component={HomeScreen} />
      <Stack.Screen name="Message" component={MessageScreen} />
    </Stack.Navigator>
  );
};

The checkUserLoggedInStatus function:

export const checkUserLoggedInStatus = (dispatch) => {
  dispatch(SET_LOADING(true)); // NOT WORKING/NOT CHANGING THE LOADING STATE
  AsyncStorage.getItem('token')
  .then((token) => {
    if (token) {
      fetch(`${API_URL}/user/`, {
        method: 'GET',
        headers: {
          ...
        },
      })
      .then(response => response.json())
      .then((data) => {
        if (data.type === 'success') {
          const details = data.data.user;
          dispatch(SET_USER_DETAILS(details)); // THIS IS WORKING FINE.
        }
      })
      .catch((error) => {
        console.log(error.message);
      });
    }
  })
  .catch((error) => {
    console.log(error.message);
  });
  dispatch(SET_LOADING(false)); // NOT WORKING/NOT CHANGING THE LOADING STATE
};

I cannot understand what I am doing wrong because I am doing the same thing with SET_USER_DETAILS action/reducer function but I don't know what's wrong with the loading state.

SUMMARY: Not able to render LoadingScreen based on loading state from UserContext.

If anyone could help me, I would really appreciate it!

Thank you!

CodePudding user response:

The problem with your implementation is that in the checkUserLoggedInStatus you are setting the loading state false outside the then and catch blocks. Since you are performing asynchronous task and need to set the loading to false after this async task is completed, you need to set the loading to false in either then or catch.

  • Related