Dispatch function instead of action
An alternative way of update state in React using function only
Before we dig into the subject I want first to make a brief description of existing solutions for state management.
The React ecosystem has a lot of different solutions for managing your state in your application.
Redux
Pros:
- The architecture completely decouple your update logic from the view
- You can easily connect some part of the Redux store to your components and so share some part of the state between multiple components without having to pass the state to children components using props
- Easy to tests since reducers are just pure functions
Cons:
- Verbose(a lot if you use TypeScript)
- Need 3rd party library for async function, there is no support for describing effects by default
Redux-Sagas:
Pros:
- All the benefits of Redux + the ability to describe effects
- Very nice usage of generator function for scheduling actions
- A complete solution out of the box
Cons:
- The learning curve is a bit high but the documentation is great to explain the different concepts that Sagas uses. Just take your time to learn it.
React hooks
Pros:
- Very simple and straightforward to use, you get your state and a setState function, it is great to handle local state in functional component
- You can use a reducer for a more complex scenario
- An async function can be handled in useEffect
- Share context using useContext
- Lots of hooks are recently written by the community
Cons:
- It looks like a class in disguise but I can understand the reasons behind such trade-off
- You can’t easily test your component anymore just by passing the state as props
Storing state using one of these solutions is great but what about updating the state? You can use setState which is fine for local state and synchronous function. Once you start with an asynchronous function you will have to use useEffect hooks with.
Redux in comparison is much more similar to an interpreter pattern where you only dispatch action and use a reducer to interpret the action as you want to update your store.
It’s a good solution but often you pay this abstraction with writing much more boilerplate. You can mitigate this in JavaScript by using a function to generate some common action but if you use TypeScript you can’t use this solution anymore and you will end up writing much more code to be type-safe.
Then when you probably want to use an asynchronous function, since it doesn’t come out of the box, you will have to install a 3rd part library.
The first 3rd part library is Redux-thunk. Redux-thunk allows you to dispatch a function(called thunk in the library) which allows you to run your effect and dispatch multiple actions inside. It’s simple to use and now you can run asynchronous code again.
Redux-sagas go further in abstraction and allow you to describe all kinds of effects you want. It relies on a generator function by yielding effects to the sagas middleware. The sagas middleware will be responsible to run the effects and give back the result to the generator function. Redux-sagas give you all the effects you would need for writing your application.
All these solutions are good but I always wondered if something that is simpler to use but as powerful as the existing solutions.
What came at first in my mind was this function signature:
type UpdateState = <S>(state: S): SThis function is simple to understand to everyone, it just takes a state S and returns a new state. Then the second question was about additional parameters or context
The response was simple, just use closure:
And the problem is solved.
It’s usually expected for a web application to have to handle asynchronous code. So let’s write the function to return a promise of the new state.
Ok, it works but there are some serious issues with this.
Let me explain a little bit. Imagine you call a function that might take some time to resolve, between the resolution of the promise and the time you called the function, the state might have changed and your resolved state contains old data with new data. In the end, you had overwritten all previous changes. Fortunately, functions are again to the rescue to solve this problem.
Problem solved again by just using functions.
Ok, that’s great, now we have a good base to update the state using functions.
Now it’s time to wonder about dispatching function and scheduling functions.
At first, let’s try to implement the scheduler by using React.setState
Now have a dispatcher and a scheduler for React setState. It is interesting to notice that our solution isn’t tied to a specific library. You could write a dispatcher for Redux or any state management library you want.
We have built the basic blocks but there is few points more to solve:
- How about deriving dispatch to transform a Dispatch<S> to Dispatch<S1> so we can easily create a child dispatcher for a child component
- How about race condition with asynchronous code?
- How about adding meta-data for debugging?
Let’s solve the first problem.
To transform a Dispatch<S> to Dispatch<S1> we need a getter from S to S1 and a setter from S1 to S
Let’s write it:
So you probably wonder why the setter is curried, after all, it doesn’t need to be. There is a lot of benefits from that especially if you are using lenses. Lenses are really useful in our context and I would recommend to use it with our solution. If you want to read more about lenses: https://github.com/gcanti/monocle-ts
Let’s write a function childDispatchFromLens:
Just by using a getter and setter we can now derive our dispatch function.
Let’s write some convenient functions to make it less verbose for simple use cases:
So now that we can do something useful with what we already built, let’s address the next issues:
- Race condition with asynchronous code
- Meta-data for debugging
For race conditions, we need to give some information on how we would like to dispatch our function. Using meta-data is a good fit for this problem, if we can annotate the function by adding one property about the dispatched function then our dispatcher would have more information about how to schedule it.
Here a little diagram to explain the expected flow:

There are two things we need to communicate to our scheduler. A flag takeLatest and a name that should be unique for this function. Adding these two pieces of information allows us to communicate our intent to the scheduler. The scheduler is responsible to read the meta-data. In that case, we communicate to the scheduler that we are only interested to apply the new state from the latest async call of this function.
Now, this part is done we can change our previous implementation a little bit:
Notice that we added a tag for the name, so meta-data are ready to be used.
Have a look at the full implementation of this idea here: