Typescript and Redux. My tips.
Tags: react, typescript, beginners, tutorial
Published: 2019-11-21 10:32:29

---
title: Typescript and Redux. My tips.
published: true
description: How to make Redux and TypeScript friendly
tags: #react #typescript #beginners #tutorial
---

## Introduction

Hello everybody!

Today I want to talk about quite popular technologies. Typescript and Redux. Both helps to develop fault tolerant applications. There is a lot of approaches to write typings of state and actions. I formed own, that could save your time.

## State

Each state in Redux should be immutable. Immutable object cannot be modified after it is created. If you forget this rule, your component do not rerender after state changes. So let's use `Readonly` modifier. It makes all properties readonly. You can't mutate property in reducer.

```ts
export type State = Readonly<{
  value: number;
}>
```

Do not forget use `Readonly` modifier for nested objects too. But what about arrays. For example:

```ts
export type State = Readonly<{
  list: number[];
}>
```

You still can change it. Let's fix it, TypeScript includes special modifier `ReadonlyArray`.

```ts
export type State = Readonly<{
  list: ReadonlyArray<number>;
}>
```

Now you can't add or remove items. You have to create new array for changes. Also TypeScript has special modifiers for Map and Set: `ReadonlyMap` and `ReadonlySet`.


## Actions

I use enums for Redux actions. Naming convention is simple: `@namespace/effect`. Effect always in past tense, because it is something  already happened. For example, `@users/RequestSent`, `@users/ResponseReceived`, `@users/RequestFailed`...

```ts
enum Action {
  ValueChanged = '@counter/ValueChanged',
}
```

## Action creators

Little magic starts.

The first thing, we use [const assertions](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions). The const assertion allowed TypeScript to take the most specific type of the expression.

The second thing, we extract return types of action creators by [type inference](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#type-inference-in-conditional-types). 

```ts
const actions = {
  setValue(value: number) {
    return {
      payload: value,
      type: Action.ValueChanged,
    } as const;
  },
}

type InferValueTypes<T> = T extends { [key: string]: infer U } ? U : never;

type Actions = ReturnType<InferValueTypes<typeof actions>>;
```

Let's improve it by helper function:

```ts
export function createAction<T extends string>(
  type: T,
): () => Readonly<{ type: T }>;
export function createAction<T extends string, P>(
  type: T,
): (payload: P) => Readonly<{ payload: P; type: T }>;
export function createAction<T extends string, P>(type: T) {
  return (payload?: P) =>
    typeof payload === 'undefined' ? { type } : { payload, type };
}
```

Then our actions object will look:

```ts
const actions = {
  setValue: createAction<Action.ValueChanged, number>(Action.ValueChanged)
}
```

## Reducers

Inside reducer we just use things described before.

```ts
const DEFAULT_STATE: State = 0;

function reducer(state = DEFAULT_STATE, action: Actions): State {
  if (action.type === Action.ValueChanged) {
    return action.payload;
  }

  return state;
}
```


Now, for all of your critical changes inside action creators, TypeScript throws error inside reducer. You will have to change your code for correct handlers.

## Module

Each module exports object like this:

```ts
export const Module = {
  actions,
  defaultState: DEFAULT_STATE,
  reducer,
}
```

You can also describe your saga inside module, if you use `redux-saga`.

## Configure store

Describe the whole state of application, all actions and store.

```ts
import { Store } from 'redux';

type AppState = ModuleOneState | ModuleTwoState;
type AppActions = ModuleOneActions | ModuleTwoActions;

type AppStore = Store<AppState, AppActions>;
```

## Hooks

If you use hooks from `react-redux`, it would be helpful too.
By default you need to describe typings each time, when you use this hooks. Better to make it one time.

```ts
export function useAppDispatch() {
  return useDispatch<Dispatch<AppActions>>();
}

export function useAppSelector<Selected>(
  selector: (state: AppState) => Selected,
  equalityFn?: (left: Selected, right: Selected) => boolean,
) {
  return useSelector<AppState, Selected>(selector, equalityFn);
}
```

Now you can't dispatch invalid action.

## The end

I hope all of this things will make your live easier.
I will glad for your comments and questions.
[My twitter](https://twitter.com/smartass3000).