# 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.