2

I had implemented useContext + useReducer and I found that I was getting re-renders when only dispatch changed.

I could have two separate components and if one dispatch was triggered both component got changed.

Example:

enter image description here

Both increment and decrement got rendered on each state update.

I found this article that I have followed but I still get the same result.

the code:

export default function App() {
  return (
    <MyContextProvider>
      <Count />
      <ButtonIncrement /> <br /> <br />
      <ButtonDecrement />
    </MyContextProvider>
  );
}

Provider:

import * as React from 'react';
import {
  InitalState,
  ApplicationContextDispatch,
  ApplicationContextState,
} from './Context';
import { applicationReducer } from './Reducer';

export const MyContextProvider = ({ children }) => {
  const [state, dispatch] = React.useReducer(applicationReducer, InitalState);

  return (
    <ApplicationContextDispatch.Provider value={{ dispatch }}>
      <ApplicationContextState.Provider value={{ state }}>
        {children}
      </ApplicationContextState.Provider>
    </ApplicationContextDispatch.Provider>
  );
};

Context:

import React, { Dispatch } from 'react';

export enum ApplicationActions {
  increment = 'increment',
  decrement = 'decrement',
  notification = 'Notification',
}

export type ActionType = ApplicationActions;

export const ActionTypes = { ...ApplicationActions };

export type StateType = {
  count: number;
  notification: string | undefined;
};

export type Action = {
  type: ActionType;
  payload?: string | undefined;
};

interface IContextPropsState {
  state: StateType;
}

interface IContextPropsDispatch {
  dispatch: Dispatch<Action>;
}

export const ApplicationContextState = React.createContext<IContextPropsState>(
  {} as IContextPropsState
);

export const ApplicationContextDispatch =
  React.createContext<IContextPropsDispatch>({} as IContextPropsDispatch);

export const useApplicationContextState = (): IContextPropsState => {
  return React.useContext(ApplicationContextState);
};

export const useApplicationContextDispatch = (): IContextPropsDispatch => {
  return React.useContext(ApplicationContextDispatch);
};

export const InitalState: StateType = {
  count: 0,
  notification: '',
};

Reducer:

import { StateType, Action, ActionTypes } from './Context';

export const applicationReducer = (
  state: StateType,
  action: Action
): StateType => {
  const { type } = action;
  switch (type) {
    case ActionTypes.increment:
      return { ...state, count: state.count + 1 };
    case ActionTypes.decrement:
      return { ...state, count: state.count - 1 };
    case ActionTypes.notification:
      return { ...state, notification: action.payload };
    default:
      return state;
  }
};

Working example here

In the article above this fiddle was presented as an example which I based my attempt on but I dont know where Im going wrong.

Note that the original example of this was done without typescript but in my attempt I am adding typescript into the mix.

CodingLittle
  • 1,761
  • 2
  • 17
  • 44

2 Answers2

2

The problem is that you are passing a new object into your context providers. It's a classic gotcha. Passing objects as props means you are passing a new object every time which will fail prop reference checks.

Pass dispatch, state directly to the providers i.e. value={dispatch}

https://reactjs.org/docs/context.html#caveats

Caveats

Because context uses reference identity to determine when to re-render, there are some gotchas that could trigger unintentional renders in consumers when a provider’s parent re-renders. For example, the code below will re-render all consumers every time the Provider re-renders because a new object is always created for value:

 <MyContext.Provider value={{something: 'something'}}>
Yuji 'Tomita' Tomita
  • 115,817
  • 29
  • 282
  • 245
  • Would you mind forking my example to illustrate – CodingLittle Aug 16 '22 at 20:04
  • Bumping this a bit. I think my troubles are typescript. I cant figure out how I can retain a State prop for state and for dispatch when I apply it directly it says that dispatch is not a function... – CodingLittle Aug 16 '22 at 20:19
  • https://jsfiddle.net/okpsfrag/1/ @CodingLittle here is one of your references modified, using a new object for dispatch (as you're doing). See how it causes re-renders. You want to avoid passing new objects into your context providers, because your context consumers use reference equality checks to know if they need to re-render, and a new object for context would always fail. – Yuji 'Tomita' Tomita Aug 16 '22 at 20:20
  • @CodingLittle well when you make this change, you have to make sure you change your whole app to respect the new structure. If you set your dispatch context provider value to dispatch vs {dispatch}, your consumers now should stop destructuring, and be const dispatch = `useContext(MyContext)` vs const { dispatch} all the way down the chain. You have quite a few utility methods so you need to update every place dispatch is referenced. – Yuji 'Tomita' Tomita Aug 16 '22 at 20:22
  • I have understood the issue. Applying dispatch is needed but if you look at my attempt here: https://stackblitz.com/edit/react-ts-pqwrx6?file=Provider.tsx I do get some issues. Sorry if im asking for to much but really struggling – CodingLittle Aug 16 '22 at 20:23
  • @CodingLittle simple, just find each place in your code (Count, Increment, Decremnet) that has `const {state|dispatch}` and remove the braces (destructuring). For the context value, you are no longer passing an object with a dispatch property and are now passing the dispatch function directly. – Yuji 'Tomita' Tomita Aug 16 '22 at 20:25
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/247317/discussion-between-yuji-tomita-tomita-and-codinglittle). – Yuji 'Tomita' Tomita Aug 16 '22 at 20:26
  • 1
    Thanks for the chat and the guidence! – CodingLittle Aug 16 '22 at 20:40
0

@Yuji 'Tomita' Tomita Was 100% on the money.

I had to change my code so that it did not wrap the state and disatch inot an object which in turn made it work as desiered.

Updated code here: https://stackblitz.com/edit/react-ts-7nuhzk?file=Provider.tsx,ButtonDecrement.tsx,App.tsx

CodingLittle
  • 1,761
  • 2
  • 17
  • 44