A user action triggers a state transition and sometimes a side effect.
I want to be able to have a reducer that return both: a representation of how the state should change and which side effects are triggered.
There are existing solutions to colocate reducer and side effects:
- https://github.com/conorhastings/use-reducer-with-side-effects
- https://redux-saga.js.org/
- and possibly others
None of them gives what I think is needed to reason about a program: a data representation of what should happen.
The reducer used in this example still takes data in and data out, nothing more. It still changes the app state. But it also adds a few extra keys that represent data needed for side effects.
The logic sits in useReducerWithEffects
: when it sees keys that are not state
it executes the side effects (given the handler map passed as a parameter) and cleans them up once they are done.
Define how each side effect is executed (your handlers
)
const handlers = { log: (dispatch, payload) => console.log(payload),
http: (dispatch, payload) => "Do an http call and then call dispatch with the result" }
So that you can write a reducer like this
function reducer(state, action) {
switch (action.type) {
case actionType.LOGIN:
return {
state: { ...state, isLoading: true },
log: action,
http: { url: "login-url", params: {}, callback: actionType.LOGIN_CALLBACK }
}
Add a hook call at the top of your application
let [state, dispatch] = useReducerWithEffects(reducer, handlers, initialState)
And the rest of your components don't need to care about side effects
Inspiration for the shape of the returned data is taken from the great citrus library (now deprecated): https://github.com/clj-commons/citrus#usage