Verbal reducers are extensions of the React useReducer hook. A verbal reducer automatically applies types to actions, and provides a mapped version of each action creator directly to the component.
This serves as an alternative to dispatch
, and is especially useful within context
providers.
npm i --save react-verbal-reducer
import { verbalReducer } from 'react-verbal-reducer'
const initialState = { count: 0 }
const reducer = verbalReducer(initialState)(
{
set: (count: number) => ({
count,
}),
increment: {},
decrement: {},
},
(state, action) => {
switch (action.type) {
case 'set':
return { count: action.count }
case 'increment':
return { count: state.count + 1 }
case 'decrement':
return { count: state.count - 1 }
}
},
)
function Counter() {
const [state, actions] = reducer.use()
return (
<>
Count: {state.count}
<button onClick={() => actions.set(state.count + 50)}>+50</button>
<button onClick={actions.increment}>+</button>
<button onClick={actions.decrement}>-</button>
</>
)
}
The core purpose of verbal reducers is to provide mapped actions directly to the component in place of React dispatch
. Just like dispatch, actions are guarenteed have a stable function identity.
The reducers, actions and (optional) default state are all defined by means of the verbalReducer
function.
Unlike regular reducers, the update object returned verbal reducers gets merged as oposed to set (similar to the class component setState
method).
const reducer = verbalReducer({ username: '', count: 0 })(
{
setUsername: (username: string) => ({
username,
}),
},
(state, action) => {
switch (action.type) {
case 'setUsername':
// results in { username: action.username, count: 0 }
return { username: action.username }
}
},
)
In the above example, the Typescript compiler will interpret the state type directly from the default definition. This could be problematic for initial state values derived from props. So, you can also apply a state type directly.
interface State {
username: string
count: number
}
const reducer = verbalReducer<State>({ username: 'guest' })(
...
)
You can then supply default state values within the component:
function Counter(props) {
const [state, { setUsername, setCount }] = reducer.use({
count: props.count,
})
}
Note: The above example also demonstrates how to deconstruct the actions and access them directly. This is usefull when you don't have a need to use the entire actions object itself.