# Use React context with reducer ## Summary Give state and dispatch produced by `useReducer()` to context created by `createContext()` so that you can access the values smoothly in any components placed under `<Context.Provider>`. ## Types ### `XxxState` The value which your context holds. ```ts type CounterState = { count: number } ``` ### `XxxAction` The reducer actions which directs how to modifies `XxxState` value. ```ts type CounterAction = | { action: 'increment'; data: { amount: number } } | { action: 'reset' } ``` ## Instances ### Initial context value Optional but useful. ```ts const initialCounterState: CounterState = { count: 0 }; ``` ### Reducer function Function modifies `XxxState` value following `XxxAction` direction. ```ts function reduceCounter(state: CounterState, action: CounterAction): CounterState { if (action.type === 'increment') { return { count: state.count + action.data.amount }; } if (action.type === 'reset') { return { count: 0 }; } return state; } ``` ### Context instance Use `React.createContext()`. ```ts const CounterContext = createContext({ dispatch: (() => undefined) as React.Dispatch<CounterAction>, state: { ...initialCounterState }, }); ``` These `dispatch` and `state` are never used as long as you use the context under provider. If you thought this should be optional, see https://github.com/DefinitelyTyped/DefinitelyTyped/pull/24509#issuecomment-382213106 Note: don't use another one; `vm.createContext()` is wrong. ## Use in components ### Root component The top level component provides context with values. 1. Wrap all with the context provider: `<XxxContext.Provider>` 2. Prepare a pair of state and dispatcher by `React.useReducer()` 3. Give them to the provider 4. Add any child components under the provider ```tsx const CounterPage: React.FC = () => { const [state, dispatch] = useReducer(reduceCounter, { ...initialCounterState }); return ( <CounterContext.Provider value={{ state, dispatch }}> <div className="CounterPage"> <h1>Counter</h1> {/* add child components here */} </div> </CounterContext.Provider> ); }; ``` ### Child components 1. Retrieve context by `React.useContext()` 2. Use values and/or invoke dispatcher To use context state value: ```tsx const ChildCounter: React.FC = () => { const { state: { count } } = useContext(CounterContext); return ( <div className="ChildCounter"> Count: {count} </div> ); }; ``` To invoke reducer function: ```tsx const CounterForm: React.FC = () => { const { dispatch } = useContext(CounterContext); const onIncrementClick = useCallback(() => { dispatch({ type: 'increment', data: { amount: 1 } }); }, []); const onResetClick = useCallback(() => { dispatch({ type: 'reset' }); }, []); return ( <div className="CounterForm"> <button onClick={onIncrementClick}>+1</button> <button onClick={onResetClick}>Reset</button> </div> ); }; ``` Make sure these components are placed under `<XxxContext.Provider>` otherwise the context values would be the initial ones always.