Redux without boilerplate

React

04 Mar 2020 | 6 minute read

Redux is a wonderful library for managing state. It's fast, deterministic and requires little configuration to get started with. But as your site grows larger and larger, chances are that your Redux reducers, actions and initial states makes your project bloated.

If you're using Typescript, this is apparent early, as you're most probably also declaring types for your reducers, actions and states as well. It the end, you might have to update about 6 files to implement a new feature.

This becomes cumbersome after a while and could be a potential source of frustration and unease, as it becomes harder to keep track of required changes when you're implementing features.

To tackle this issue, and to make development in Redux more efficient, there is a neat (official) package called Redux Toolkit.

Introduction to Redux Toolkit

To explore Redux Toolkit, let's build a basic reducer for our dummy e-commerce. To display the products for our customers, we need a state that holds all of our products, and we need to be able to update how many products we have in stock (onHand) for each product.

First of all, let's declare the types for our products, state and initialState.

interface Product {
  sku: string;
  onHand: number;
}

export interface ProductsState {
  products: Product[];
}

const initialState: ProductsState = {
  products: [],
};

Now, let's implement our reducer. To do so, we will utilise the createSlice from the Redux Toolkit library. We'll also implement a case reducer function to update the onHand value for a specific product as well.

import { createSlice } from '@reduxjs/toolkit';

const productsSlice = createSlice({
  name: 'products',
  initialState: initialState,
  reducers: {
    updateOnHand(state, action): void {
      const { sku, onHand } = action.payload;
      const product = state.products.find(
        (product): boolean => product.sku === sku,
      );
      if (product) {
        product.onHand = onHand;
      } else {
        state.products.push({ sku, onHand });
      }
    },
  },
});

Putting it all together, and exporting our actions and reducers, our final code looks like this:

import { createSlice } from '@reduxjs/toolkit';

interface Product {
  sku: string;
  onHand: number;
}

export interface ProductsState {
  products: Product[];
}

const initialState: ProductsState = {
  products: [],
};

const productsSlice = createSlice({
  name: 'products',
  initialState: initialState,
  reducers: {
    updateOnHand(state, action): void {
      const { sku, onHand } = action.payload;
      const product = state.products.find(
        (product): boolean => product.sku === sku,
      );
      if (product) {
        product.onHand = onHand;
      } else {
        state.products.push({ sku, onHand });
      }
    },
  },
});

export const { updateOnHand } = productsSlice.actions;

export default productsSlice.reducer;

Using Redux Toolkit, all of our types, actions and reducers are placed in one file. Neat!

If you're a Typescript developer with a keen eye, you might have noticed that our actions aren't typed in the example above. Redux Toolkit has great support for usage with Typescript, and I invite you to dive deeper into how they can be used together.