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:
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.
CodePudding user response:
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'}}>
CodePudding user response:
@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