Modern React State Management with Overmind.js
Cover image by Theodor Moise on Pixabay
Handling state in an application is like juggling with data. This article is about a very interesting way of handling state in your React app with Overmind.js
Why Overmind?
There are many tools for state management out there like Context-API, Redux, MobX or MST. So why we want another one?
In my latest client project I used context based state together with Apollo Client. I'm a huge fan of React Hooks and together with Context-API it's a very nice way to handle state in your application. It seemed to me that I didn't need any bigger state management solution like Redux anymore. In the beginning this was totally fine and I was happy with this approach but after two months the app became bigger and complexity was growing. I wasn't happy anymore and I decided it's time to switch to a different solution.
Back in the days I used and liked Redux a lot and I felt very comfortable with it, but it always had a smell of "too-much-code" for small stuff. On the other hand I never really used MobX but heard only good things about it. After doing some research I found a new library called Overmind which locked pretty interesting.
Combining the best
Overmind was created by Christian Alfoni with the goal to give the best developer experience as possible together with strong TypeScript support. The framework internals are hidden to the developer and the API is very simple and straight forward.
- strong TypeScript support
- very simple API
- good documentation and easy to learn
- great devtools
I think one of the best things you get with Overmind is that you have fully typed code nearly for free.
Defining state
Define your state as a simple object. Even if it is Typescript you can define your state as simple like this:
const state = {// title: stringtitle: '',// count: numbercount: 0,// foo: Foo | nullfoo: null as Foo | null,}
HINT: Don't use undefined or ? in your state because it is just considered as an anti pattern. Any undefined value on a state will not show up in the devtools, it will not be passed to server or saved in local storage.
Everywhere you use the state you have full TypeScript support and code completion. SIMPLE, right?
Derived state
Derived state values are computed values based on other state. In Overmind you define your derived state directly next to the state.
Here we define a computed state variable, doubleCount. It is important to note that the function is memoized by default and runs only when count has changed.
const state = {count: 0,// memoized function that only executes when count changedoubleCount: state => state.count * 2,}
In Redux you would have to write selectors and use libraries like Reselect to memoize the calculation. But not in Overmind, it's already included. SIMPLE, right?
State mutation
All state mutations are done with actions. Actions have access to your state and can directly change properties.
function incCount(state) {state.count = state.count + 1}function resetCount(state) {state.count = 0}function setCount(state, value) {state.count = value}
HINT: You can't mutate state outside the actions, this means you can't accidentally try to change them in your views.
There is no spread madness like in Redux that comes with immutability. Just change what you want to change. SIMPLE, right?
Side effects
Effects allow you to decouple your app completely from 3rd party APIs. You can read more about them here: overmind effects.
From the docs: Developing applications is not only about managing state, but also managing side effects. A typical side effect would be an HTTP request or talking to localStorage. In Overmind we just call these effects.
Effects should be "initialized" in the onInitialize function of Overmind. There you can provide them with all stuff they need like getters for getting current state or actions to execute.
export const onInitialize = ({ state, effects, actions }) => {effects.myCoolEffect.initialize({getSomeState: state.partOfState,onMoviesLoadSuccess: actions.setMovies,})}
Access state and actions in components
To get state in a component you have to connect it to Overmind. This is done with the useOvermind hook that provides state and actions. All you have to do is to deconstruct the hook with the properties you want.
function Counter() => {// hint: you get fully typed state and actions hereconst {state: { count },actions: { incCount }} = useOvermind()return (<div>Count: {count}<button onClick={incCount}>INC</button></div>)}
This is all? Yes, it is nuts how easy it is to get state and actions into your components. But wait: How can we prevent the component from rerendering when other parts of the global state have changed, like the title? Our component is only interested in the count property and only wants to rerender if this value changes. Guess what: Overmind nows exactly which parts of the state the component is using and updates the component only when this part of the state changes. SIMPLE, right?
Mutation tracking
But how does this work, you will think? Overmind is using mutation tracking instead of immutability, you can read more about this concept here: immutability vs. mutation tracking.
Christian Alfoni: How you detect the changes and update the UI should just work and be performant! With mutation tracking you put your faith in the system. It relieves you of the mental load of figuring out how components update, it just works and optimally so.
Powerfull devtools
Overmind comes with very powerfull devtools. You can use the VSCode extension or use the standalone version
npx overmind-devtools
All your state and derived state values are visible, it's even possible to change them directly inside the tool. You can also see all executed actions with their payload and what part of the state they changed. Sending an action? Sure, you can do this as well.
Overmind theats the view part of your app just as an implementation detail. You can write and execute your whole application logic without any views, just with the help of the devtools. This is amazing and SIMPLE, right?
Functional programming style
I'm a big fan of functional programming paradigms, that were introduced with React and became the default in the React world after the release of hooks. Overmind perfectly fits into this. You write only functions, there is no need for classes. When I had a look in MobX this was a big downside for me because all the examples use classes and I don't want to use classes anymore if possible.
What about pure functions? Mhhh yes sure, Overmind actions are not as pure as regular reducers. In practice it's not a real downside to me because you can test your actions very easy anyway.
Documentation and learning curve
Overmind has a very good documentation. The framework internals are hidden to the developer and the api is very simple and straight forward. There is no additional hidden stuff to learn like reselect, redux-sagas, redux-thunk etc.
Cool side note: I read the Overmind docs for the first time, started to give it a try and after 3 hours I had refactored my whole app. This was super easy. And I was very confident to do the changes because I used react-testing-library. The tests were testing the functionallity of the components without implementation details. Just some minor changes in the test initialization were necessary.
I think after few hours you will feel very comfortable too. Back in the days it took me weeks to wrap my head around Redux. I also tried MobX and it is much harder to read through the docs and to understand all this observer and observable stuff.
Running examples
But enough theory, let's have a look on a working example on Codesandbox:
Codesandbox Example TypeScript
But how does it scale in a big real world project? It scales very well. Codesandbox is currently moving to Overmind. Check their Github Repo to have a better impression how this will look like in a big application.
Summary
I'm so happy that I found Overmind, it is really a lot of fun to use and it simplyfied my app completely. I hope this article can help to convince you to give it a try.