DEV Community

Xuan
Xuan

Posted on

I create a package to simplify the work when using useReducer

Using useReducer to handle complicated state is preferable than using useState. However, writing a reducer is kind of annoying because we may need to write so many switch/case to determine which action we are going to handle. Furthermore, we may prefer writing action creators to generate actions instead of writing an action directly.

To solve these problems, I write a package called use-case-reducers to simplify our work.

Features of this package

  1. Use an object to generate a reducer
  2. Automatically generate action creators
  3. Allow mutating state in case reducer

Use an object to generate a reducer

Instead of writing a reducer function, use-case-reducers use an object containing case reducers to generate a reducer. A case reducer is a function that only handles one case of actions. For example, if we want to handle a counter state with two actions increment and add, then the object may look like:

const caseReducers = {
  increment: state => ({count: state + 1}),
  add: (state, amount) => ({count: state + amount}),
}
Enter fullscreen mode Exit fullscreen mode

Automatically generate action creators

use-case-reducers will generate all action creators corresponding to the case reducers you pass in. Take the above caseReducers for example, it will generate two action creators increment() and add(amount).

Allow mutating state in case reducer

This package use immer in the generated reducer, so we can mutate the state inside our case reducers. We can rewrite the above caseReducers to:

const caseReducers = {
  increment: state => {state.count += 1},
  add: (state, amount) => {state.count += amount},
}
Enter fullscreen mode Exit fullscreen mode

This feature may be useful when our state is very complicated.

How to use this package

Use npm or yarn to install it as a dependency:

npm install use-case-reducers
#or
yarn add use-case-reducers
Enter fullscreen mode Exit fullscreen mode

We are going to write a component with a counter state and use this package to update it.

import useCaseReducers from 'use-case-reducers'

const initialState = {count: 0};
const caseReducers = {
  increment: (state) => {state.count += 1},
  add: (state, amount) => {state.count += amount},
};

const Counter = () => {
  const [state, dispatch, {increment, add}] = useCaseReducers(caseReducers, initialState);

  return (
    <div>
      count: {state.count}
      <button onClick={() => dispatch(increment())}>increment</button>
      <button onClick={() => dispatch(add(10))}>add 10</button>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Let's look the same component but writing with useReducer.

import {useReducer} from 'react'

const initialState = {count: 0};
const reducer = (state, action) => {
  switch(action.type) {
    'increment': {
      return {count: state.count + 1};
    }
    'add': {
      return {count: state.count + action.payload};
    }
  }
}

const increment = () => ({type: 'increment'});
const add = (amount) => ({type: 'add', payload: amount});

const Counter = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      count: {state.count}
      <button onClick={() => dispatch(increment())}>increment</button>
      <button onClick={() => dispatch(add(10))}>add 10</button>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

As you can see, we can write less code with use-case-reducers. Hope this package will help you. Have a good day!

Top comments (5)

Collapse
 
mengxuanny profile image
Mengxuan

This is cool! I'm excited to try it out!

Collapse
 
jason89521 profile image
Xuan • Edited

Thank you, and if you encounter any issues while using it, please feel free to let me know.

Collapse
 
mengxuanny profile image
Mengxuan

Hi, I tried it just then. It makes my life much easier by avoiding writing actions and reducer function. However I did encounter an issue. In one of my applications I used Map to store data in the state and this error came back:
[Immer] The plugin for 'MapSet' has not been loaded into Immer. To enable the plugin, import and call enableMapSet() when initializing your application.
I imported and called enableMapSet() in both main.jsx and App.jsx however the error is still there...Do you have any ways to solve this please?

Thread Thread
 
jason89521 profile image
Xuan

I suppose it might be due to the difference in our immer versions. The immer version used in "use-case-reducers" is 9.0.12, so if you install this version, you shouldn't see the error message. However, using Map or Set as state doesn't seem ideal. Have you considered using plain objects instead?

reference

Thread Thread
 
mengxuanny profile image
Mengxuan

Thank you! I refactored the code and it now works!! Big fan of this package will recommend it to anyone who might be confused about the usage of useReducer. Keep up the good work:)