Easy type safety with useDispatch and useSelector
Tags: react, redux, typescript
Published: 2020-02-18 16:58:24

(Update: the snippet in this article now has a package: [react-redux-typed-hooks](https://www.npmjs.com/package/react-redux-typed-hooks))

(Update 2: just use the types provided by @types/react-redux:

```tsx
import * as RR from 'react-redux'

type StoreEvent = ReviewStoreEvent
interface Store {
  reviews: ReviewStore
}

export const useSelector: RR.TypedUseSelectorHook<Store> = RR.useSelector
eport const useDispatch = () => RR.useDispatch<Dispatch<StoreEvent>>()
```

And turn on typescript's `strict` mode to make sure you're using the typed hooks!)

Adding types to Redux can be done in various ways with varying level of overhead and type safety. Some suggestions use enum type definitions for actions instead of string identifiers, some other sources use _action creators_. Both approaches suffer from these drawbacks:

- It adds overhead; in case of action creators, you don't see the event shape immediately in the code.
- It still doesn't prevent the developer from passing an arbitrary action object to the dispatch call.

(For proper term usage, from here I'll use the word _event_ instead of _action_.)

Wouldn't it be nice if we could use the good old plain event objects, yet being fully safe from typos, or any kind of non-existent or misshaped events? And if we're at that, can we get the same level of type safety when selecting a chunk from the store with `useSelector`?

The answer is yes, and here I'll show how to do this.

As David Khourshid highlights it in his [excellent post](https://dev.to/davidkpiano/redux-is-half-of-a-pattern-1-2-1hd7), in TypeScript, _discriminated unions_ are a very good way to define well-formed store and event objects. Let's say we have a FruitStore and a corresponding event type:

```tsx
export interface FruitStore {
  status: 'init' | 'loading' | 'loaded';
  pageSize: 25 | 50 | 100;
  data: FruitRecord[];
}

export type FruitStoreEvent =
  | { type: 'FRUITS_LOADING' }
  | { type: 'FRUITS_LOADED'; data: FruitRecord[] }
```

And we have a reducer too, of course:


```tsx
const initial: FruitStore = {
  status: 'init',
  pageSize: 25,
  data: []
}

export default (
  state: FruitStore = initial,
  event: FruitStoreEvent
): FruitStore => {
  switch (event.type) {
    case 'FRUITS_LOADING':
      return {
        ...state,
        status: 'loading'
      }
    case 'FRUITS_LOADED':
      return {
        ...state,
        status: 'loaded',
        data: event.data
      }
    default:
      return state
  }
}
```

The challenge now is to enforce dispatch calls to only receive well-formed events. If you import `useDispatch` directly from `react-redux`, there's no way to have any restriction on what kind of events are sent. In order to enforce proper types in the dispatch calls, we introduce our own `useDispatch` hook in the store:

```tsx
import { useDispatch as _useDispatch } from 'react-redux'

export function useDispatch() {
  const dispatch = _useDispatch()
  return (event: FruitStoreEvent) => {
    dispatch(event)
  }
}
```

As we probably will have more than one reducers, it's better to put this hook in the main Redux file, and have an aggregated event type:

```tsx
// store/index.ts

import { createStore, combineReducers } from 'redux'
import { useDispatch as _useDispatch } from 'react-redux'

import fruits, { FruitStoreEvent } from './fruits'
import others, { OtherStoreEvent } from './others'

type StoreEvent = FruitStoreEvent | OtherStoreEvent

export function useDispatch() {
  const dispatch = _useDispatch()
  return (event: StoreEvent) => {
    dispatch(event)
  }
}

export default createStore(
  combineReducers({
    fruits,
    others
  })
)
```

Then we only have to import `useDispatch` from the store, instead of Redux:

```tsx
// components/mycomponent.tsx

import { useDispatch } from '../store'
```

We're done with the dispatch side!

Now let's add types to `useSelector` too. This is a bit tricky, because we don't know what type comes out from the useSelector callback; but if we add type to the store root, TypeScript will know, and we can forward that information to our hook's return type with generics:

```tsx
import { useSelector as _useSelector } from 'react-redux'

interface Store {
  fruits: FruitStore;
  others: OtherStore;
}

export function useSelector<T>(fn: (store: Store) => T): T {
  return fn(_useSelector(x => x))
}
```

Now our store variables are properly typed.

Let's put everything together:

```tsx
// store/index.ts

import { createStore, combineReducers } from 'redux'
import {
  useDispatch as _useDispatch,
  useSelector as _useSelector
} from 'react-redux'

import fruits, { FruitStore, FruitStoreEvent } from './fruits'
import others, { OtherStore, OtherStoreEvent } from './others'

type StoreEvent = FruitStoreEvent | OtherStoreEvent

interface Store {
  fruits: FruitStore;
  others: OtherStore;
}

export function useDispatch() {
  const dispatch = _useDispatch()
  return (event: StoreEvent) => {
    dispatch(event)
  }
}

export function useSelector<T>(fn: (store: Store) => T): T {
  return fn(_useSelector(x => x))
}

export default createStore(
  combineReducers({
    fruits,
    others
  })
)
```

And that's it. The only thing we have to watch out is to import `useDispatch` and `useSelector` from our store, not from Redux.