1

I need to share pieces of information across my app, like user details, avatar, and in-app alerts. I'm concerned I might not be using useReducer and useContext the right way since I'm just subscribing everything to this one reducer and using it as a giant global state-setting function. Should I be approaching this differently?

const initialState : IStore = { isLoading : true, isSignedIn : false, newUser : false, hero : null, alerts : [] };
const store = createContext<IStore>(initialState);
const { Provider } = store;

const StateProvider : React.FC = ( { children } ) => {
  const [state, dispatch] = useReducer((state : IStore, action : IAction) => {

    switch(action.type) {
      case 'TOGGLE LOADING': {
        const { isLoading } = action.payload;
        if(typeof isLoading === 'undefined'){
          return Object.assign({}, state, { isLoading : !state.isLoading });
        }
        return Object.assign({}, state, { isLoading });
      }
      case 'SET EXISTING USER INIT DATA': {
        const { user, hero, gameItems, latestBattle, isSignedIn } = action.payload;
        return { ...state, ...{ user, hero, gameItems, latestBattle, isSignedIn } };
      }
      case 'SET ISSIGNEDIN': {
        const { isSignedIn } = action.payload;
        return { ...state, isSignedIn };
      }
      case 'SET NEW USER': {
        const { newUser } = action.payload;
        return { ...state, newUser };
      }
      case 'SET HERO': {
        const { hero } = action.payload;
        return { ...state, hero };
      }
      case 'SET USER': {
        const { user } = action.payload;
        return { ...state, user };
      }
      case 'SET ALERTS': {
        const { alerts }  = action.payload;
        return { ...state, alerts : [...alerts] };
      }
      case 'REMOVE ALERTS': {
        const { indiciesForRemoval } = action.payload;
        return { ...state, alerts : 
          state.alerts.filter((alert) => {
            return !indiciesForRemoval.includes(alert.index);
          })
        };
      }
      default:
        throw new Error('In Store default, should not happen.');
    };
  }, initialState);

  return (
    <Provider value={{ state, dispatch }}>
      {children}
    </Provider>
  );
};
Bryson Kruk
  • 281
  • 4
  • 12

1 Answers1

1

It's more of a question of code quality:

  • Does it make sense to store all this data and the related actions in this single reducer?
  • Would be more logical and/or maintainable to split the reducer into several reducers instead?
  • If I move part of this reducer into a separate reducer, could it be reused elsewhere?

If you're only using a bit of state and only a handful of actions, a single reducer is just fine. One thing you have to look out for is re-render triggering: whenever your reducer's state gets updated, every component making use of that reducer will have to be re-rendered. For small states, not a problem, but if you e.g. have a lot of complex components needing state.fieldA and a lot of complex components needing state.fieldB, then it might be worth splitting. Otherwise updating fieldA would also trigger a re-render for all the components that are only interested in fieldB. Although certain optimizations could be made to partially counteract that.

Kelvin Schoofs
  • 8,323
  • 1
  • 12
  • 31
  • Thanks for this. One follow up question, I'm already `` to give the entire app access to the reducer/store. If I broke it into multiple different reducers, would I just wrap specific child components with a different Provider? – Bryson Kruk Jul 25 '21 at 17:15
  • 1
    You can still put all the providers at the top level, wrapping ``. Or like you said, you can wrap specific child components, but of course other components that aren't a descendant of the provider can't make use of the same provider. I don't think it actually has a performance impact. At least for the providers, it definitely matters where/how your consumers/`useContext` are used, as that's where the re-renders happen. – Kelvin Schoofs Jul 25 '21 at 17:18