use-case-reducers
v0.0.9
Published
A simplified version of React's useReducer, use this package to generate all actions automatically.
Downloads
4
Maintainers
Readme
Use Case Reducers
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.
dispatch
import { dispatch } from 'use-case-reducers';
const DispatchContext = React.createContext(dispatch);
This function is useful when you want to provide a default context because its type is the same as useCaseReducers
and useReducer
's returned dispatch
.
If you don't have a provider, this function will throw an error.