Skip to content

Latest commit

 

History

History
242 lines (185 loc) · 8.03 KB

README.md

File metadata and controls

242 lines (185 loc) · 8.03 KB

Use Case Reducers

npm

useCaseReducers simplifies the work when you are using React's useReducer. Its api is almost the same as useReducer, so there are just a few things that you need to learn if you have already been familiar with useReducer.

Get started

npm install use-case-reducers
#or
yarn add use-case-reducers

Why use this package

Although useReducer is great, writing a reducer is kind of annoying, especially when we need to handle more actions. Suppose we need to handle 10 actions with our state, then we need to write 10 switch/case of 10 if/else to deal with these actions. Sounds terrible, right?

Furthermore, when we use useReducer, we probably don't want to dispatch an action by writing dispatch({type: 'addTodo', payload: newTodo}). The common use case we prefer may be writing an action creator for each action. For example, we may write:

// An action creator returns the action of adding a to-do
const addTodo = newTodo => ({ type: 'addTodo', payload: newTodo });
// Dispatch a action of adding a to-do by passing a action creator
dispatch(addTodo(newTodo));

Action creators help us writing cleaner code. But again, what if we need to handle so many actions? We definitely don't want to write these action creators manually, right?

useCaseReducers comes to the rescue! With useCaseReducers, we don't need to write a lot of switch/case and a lot of action creators. All we need to do is passing an object of case reducers, then useCaseReducers will generate a reducer and all action creators automatically.

What is a case reducer

The difference between a case reducer and a normal reducer is that a case reducer only handles one action while a normal reducer handles all actions. For example, if we use a reducer to handle a counter state, we may write:

const reducer = (state, action) => {
  switch (action.type) {
    case 'increment':
      return state + 1;
    case 'decrement':
      return state - 1;
    case 'add':
      return state + action.payload;
    case 'sub':
      return state - action.payload;
  }
};

We can split the above reducer into 4 case reducers:

const increment = state => state + 1;
const decrement = state => state - 1;
const add = (state, amount) => state + amount;
const sub = (state, amount) => state - amount;

As you can see, writing a case reducer is very easy.

API Reference

This package uses Immer, so you can mutate the state in your case reducers.

useCaseReducers

import useCaseReducers from 'use-case-reducers';

const [state, dispatch, actions] = useCaseReducers(caseReducers, initialArg, init);

The differences between useCaseReducers and useReducer are the first parameter and there is a third returned value in useCaseReducers. Instead of passing a normal reducer to the first parameter, useCaseReducers accepts an object which contains all case reducers. The third returned value is an object which contains all action creators generated by the caseReducers you pass in. Here is an example of how to use it:

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

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

  return (
    <div>
      count: {count}
      <button onClick={() => dispatch(increment())}>+</button>
      <button onClick={() => dispatch(decrement())}>-</button>
      <button onClick={() => dispatch(add(10))}>add 10</button>
      <button onClick={() => dispatch(sub(10))}>minus 10</button>
    </div>
  );
};

Lazy initialization

Like useReducer, you can also create the initial state lazily by providing an init function as the third parameter. The initial state will be set to init(initialArg).

The parameters for a case reducer

A case reducer can accepts an arbitrary number of parameters. Note that if a case reducer accepts more than one parameter, the first parameter is the state, and the rest parameters are the payload of a action. Here is an example:

const caseReducers = {
  // zero parameter
  reset: () => {
    return { count: 0 };
  },
  // one parameter, that is, it only accepts state
  increment: state => {
    state.count += 1;
  },
  // two parameters, the second is the payload
  add: (state, amount) => {
    state.count += amount;
  },
  // the number of the payload parameters is not constrained,
  // that is, you can specify an arbitrary number of parameters to be the payload
  addTwo: (state, amount1, amount2) => {
    state.count += amount1 + amount2;
  },
};

createCaseReducers

import { createCaseReducers } from 'use-case-reducers';

const { initialState, caseReducers } = createCaseReducers(_initialState, _caseReducers);

If you are a typescript user, write a plain object of case reducers may be verbose. For example, if we want to write an object of case reducers to handle a state whose type is number, we should write something like the following code:

const caseReducers = {
  increment: (state: number) => state + 1,
  decrement: (state: number) => state - 1,
  add: (state: number, amount: number) => state + amount,
  sub: (state: number, amount: number) => state - amount,
};

As you can see, although the type of the state is the same, we need to specify its type for every case reducer.

An alternative solution is to use createCaseReducers. It can generate a well type defined object without specifing the type of state for every case reducer. Here is an example of how to use createCaseReducers:

const { initialState, caseReducers } = createCaseReducers(0, {
  increment: state => state + 1,
  decrement: state => state - 1,
  add: (state, amount: number) => state + amount,
  sub: (state, amount: number) => state - amount,
});

Note that this function just simply returns the _initialState and _caseReducers you pass in. It can be helpful when you are using typescript.

createSlice

import { createSlice } from 'use-case-reducers';

const { initialState, reducer, actions } = createSlice(_initialState, caseReducers);

If you want to use React's useReducer directly, then this function may be helpful for you. It will generate a reducer and all actions creators you need. Here is an example of how to use:

const {
  initialState,
  reducer,
  actions: { increment, decrement },
} = createSlice(0, {
  increment: state => state + 1,
  decrement: state => state - 1,
});

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

  return (
    <div>
      <div>{state}</div>
      <button onClick={() => dispatch(increment())}>+</button>
      <button onClick={() => dispatch(decrement())}>-</button>
    </div>
  );
};

createActions

import { createActions } from 'use-case-reducers';

const actions = createActions(caseReducers);

If you only want to use action creators from your caseReducers, you can pass it to this function, then this function will return an object contains all action creators.

createReducer

import { createReducer } from 'use-case-reducers';

const reducer = createReducer(caseReducers);

This function will return a reducer function generated by the caseReducers you pass in.