React Context: One way to do it
Tags: reaect, reactnative
Published: 2020-01-17 06:27:56

If you haven't already read [Kent C. Dodds' post on React Context](https://kentcdodds.com/blog/how-to-use-react-context-effectively), do it now.

I've since tweaked it to my own liking.

```typescript
import * as React from "react";

import { useTicTacToe } from "./hook";

export const TicTacToeContext = React.createContext<
  ReturnType<typeof useTicTacToe>
>(undefined);

export const TicTacToeProvider: React.FC = ({ children }) => (
  <TicTacToeContext.Provider value={useTicTacToe()}>
    {children}
  </TicTacToeContext.Provider>
);

export const useTicTacToeContext = () =>
  React.useContext(TicTacToeContext) || missingProvider();

const missingProvider = () => {
  throw new Error(
    "useTicTacToeContext must be used inside a TicTacToeProvider"
  );
};

// SELECTORS
export const useBoard = () => useTicTacToeContext().board;
export const useCurrentPlayerId = () => useTicTacToeContext().currentPlayerId;
export const useGameOver = () => useTicTacToeContext().gameOver;
export const useTied = () => useTicTacToeContext().tied;
export const useWinnerId = () => useTicTacToeContext().winnerId;
export const useWinningSquares = () => useTicTacToeContext().winningSquares;
export const useMove = () => useTicTacToeContext().move;
export const useNewGame = () => useTicTacToeContext().newGame;

```

## Keep it stupid simple

For me, the context's sole job is to to provide the return value from a hook, to all its children. So, in this context, the hook is imported, passed into the context....and that's it.

## Feel free to not shoot yourself in the foot

The context defaults to undefined. The [react docs](https://reactjs.org/docs/context.html#reactcreatecontext) indicate that you can default to any value.

> The defaultValue argument is only used when a component does not have a matching Provider above it in the tree.

Yeah, this might make is easier to test, but when you can render the provider in the test, it's not *that* much easier. Besides, [easy is the enemy of simple](https://twitter.com/richardbranson/status/242582296157384704). Defaulting to undefined,

```typescript
export const TicTacToeContext = React.createContext<TicTacToeContextType>(
  undefined
);
```
combined with
```typescript
export const useTicTacToeContext = () =>
  React.useContext(TicTacToeContext) || missingProvider();

const missingProvider = () => {
  throw new Error(
    "useTicTacToeContext must be used inside a TicTacToeProvider"
  );
};
```

prevents you from rendering the consumers outside of a provider. I haven't yet found a good reason to render a consumer without rendering a provider. If you know of any, let me know. Thanks.

Lastly, a few selectors to make reading the hooks a little nicer.

## A few other tidbits:

### Typescript helper

```typescript
ReturnType<typeof useTicTacToe> // the type of the return value of the useTicTacToe
```

### Performance

The only time I consider renders is when there is a callback that might be used in a `useEffect` and mess up the deps array. If the callback is going into a component that doesn't `useEffect`, like an `<input>`, then I don't bother with any performance optimizations.

## Conclusion

Well, this is one way to do it.

What's your way?