Observer Design Pattern for Atomic State Management
State management lies at the core of modern web development, especially when working with complex user interfaces.
In JavaScript and React applications, handling state efficiently is crucial for building scalable and maintainable solutions.
One powerful and extremly common design pattern that is used significantly for state management capabilities is the Observer design pattern, sometimes also referred as the Pub-Sub design pattern.
In this blog, I’ll dive into how observer pattern can be used to create an atomic state manager in JavaScript and create an equivalent binding for React.
The term “atomic” referes to the idea of treating each state as a seperate self contained entity, much like atom (which are the building blocks of matter).
In the context of front-end development, especially in frameworks like React, atomic state management is about breaking down the application state into smaller, manageable pieces that are scoped to specific components or modules.
So let’s recreate one for ourselves.
Defining the state model
Let’s define our atom (or individual state) through the basic properties of a observer design pattern alongside the essentials needed for a typical state manager, i.e, :
get()
: a method to get the state value.set()
: a method to set/update with a new state value.subscribe()
: a method which takes a callback to subscribe to store changes and also returns a function to clear the subscriptions created.
Now, that we have the interface of our AtomStore
, let’s define the actual implementation using which we can create the atomic store.
Here’s a small breakdown explaining what we did above:
-
createAtom
: is a function which takes aninitialValue
for the store and returns anAtomStore
instance. We do this, so we can leverage scopes and closures in JavaScript, making the valuevalue
available even when thecreateAtom
function completes executing. Also each atom stores thier own respective values, so there is no chance of value collision between multiple stores. -
get
: method that simply returns whatever is currently saved on the store, i.evalue
-
set
: method that takes anewValue
for the store and replaces the current value, i.evalue
-
subscribe
: method that takes acallback
and saves them onto thesubscribers
list and returns a cleanup function to clear that subscription. We are using set here, to we can remove deuplicate subscriptions passed to the store, the same can be achieved by using arrays for maintaing subscription list as well.
Now, with following considerations in mind, we can create a atomic store as follows:
Understanding subscription callback
Although the above implementation is absolutely correct, we still have one crucial piece missing.
If you notice, we need to invoke atom.get()
everytime when we need to see the latest state. This also means that when we are not aware about how the state might be changed, we might run into using old values of the state.
This is where the observer pattern shines, which helps us to know about the latest changes on the store and “react” to it when needed. We have already considered this when defining our store interface, so we’ll update our implementation to actually make use of it.
I’ll just highlight the new changes.
All we’re doing here, is simply on every change of state value, i.e call of set(newValue:T)
method, we iterate over all the subscriptions we have added and simply pass the new value to theme.
Congratulations! You just created your own atomic state manager and learned about leveraging a very popular design pattern. 🎉🚀 .
The next section will focus only on creating a binding of the same atomic state to be used with React, in case you want to follow along.
Creating Bindings for React
So, up until now, we create a pure Vanilla atomic state manager, which abilities to get, set and listen to store changes.
The binding for React will simply link these methods returned from the store, to the React internals like “state”, so our UI can react to the changes.
With that in mind, let’s create a hook implementation for the atomic store.
Here’s the breakdown of what we did above:
atom.get()
: we use state returned by atom as the initialValue for React’s useState hook.atom.subscribe()
: we leverage React’s useEffect hook, to create a subscription to the atom store and ask it to invoke React’s stateState function, so it can render the component.atom.set()
: we return this as a setter function to update the store value with new state.
A very small example demonstrate how all comes together:
Conclusion
That’s it. That’s all it takes to create an atomic state manager in pure JavaScript and create bindings in React.
If throughout the blog, even for a second you were wondering why does the API signature look very similar to something on worked on before? That’s because it is. 😀
The signature and naming is similar to a very popular atomic state manager called Jotai.
Now, Jotai is much more complex, intricate and feature rich compared to what we did here, so our version does not stand a chance against it. 😅 The goal was to understand the fundamentals of a practical usage of observer design pattern while creating something fun and most likely to be used.
I have used Jotai in production before and literally have no complaints with the library. It’s perfect option when it comes to handling small atomic states (expecially synchronous state). If you have not used Jotai before, I highly recommend it.
Do give the Jotai project a like on Github and follow their maintainers to learn more about it.
Keep Learning! Take Care!