Why State Management?
To understand state management, we need to understand how we got here. How did web development evolve to need state management? Or did we always manage state in our application?
In the early days of the web, most websites were static, meaning they consisted of a series of HTML pages that were served to the client by a server. These pages were relatively simple, and the state of the application was largely self-contained within each page.
As the web evolved and became more interactive, web applications began to incorporate more complex features and functionality. This required developers to find ways to manage the increasing amount of data and state that was being used by these applications.
One early approach to state management was to use cookies, which are small pieces of data that are stored on the client's computer and sent back to the server with each request. This allowed web applications to store and retrieve data between requests, but it was a limited and somewhat clunky solution.
With the rise of JavaScript and the ability to build more interactive and dynamic web applications, developers began to use more sophisticated approaches to state management. This included the use of client-side storage technologies such as local storage and session storage, as well as the adoption of frameworks and libraries that provided tools for managing state, such as React and Redux.
To understand how these work, and to further understand how state management works, we need to understand some patterns that are implemented by state management libraries.
The Observer Pattern
The observer pattern defines a one-to-many dependency between objects, such that when one object changes state, all of its dependents are notified and updated automatically. This pattern is also known as the publish-subscribe pattern (or the Pub/Sub Pattern).
In this pattern, the object that is being observed is known as the "publisher," and the objects that are subscribed to the publisher are known as "subscribers." When the publisher's state changes, it "publishes" a notification to all of its subscribers, who can then "subscribe" to the notification and react to the change.
Libraries that use the Observer Pattern:
- Recoil
- MobX
- RxJS
The Context Pattern
In the context pattern, a global context object is created and made available to any component that needs access to it. This context object can store data that is shared between components, such as application-wide settings or the current user's session data.
Components that need access to the context object can "subscribe" to it, and will be notified whenever the data in the context object changes. This allows the components to react to changes in the data automatically, rather than having to manually manage the flow of data between them.
Libraries that use the Context Pattern:
- Jotai
- Zustand
The Command Pattern
In the command pattern, a command object is created to represent a request or action. The command object typically includes all of the information needed to execute the request, such as the method to be called and any necessary parameters.
Imagine that you have a software application that needs to perform a specific action, such as sending an email or saving data to a database. Rather than calling the action directly, you can use the command pattern to create an object that represents the action.
This command object would include all of the information needed to execute the action, such as the method to be called and any necessary parameters. The command object can then be executed directly, or it can be passed to other objects for execution.
This approach allows you to decouple the action from the objects that execute it, making it easier to modify or extend the behavior of the application. It also allows you to create a more modular and flexible design, as you can easily add or remove different actions by creating or modifying command objects.
Libraries that use the Command Pattern:
- Redux
- Flux
The Million Dollar Question
When do I use what?
This is absolutely opionionated. From my experience, this is what I've come to realize.
- If you have a lot of components that need to react to change of a small piece of state i.e, your one to many relationship has too many listeners, Observer Pattern would work best. You're probably good to use something like Recoil.
- If a lot of the changes are predefined, like toggles, checkboxes, modals etc, Command Pattern would work best. You're probably good to use something like Redux.
- If the complexity of your app is fairly limited and you want to move fast with little mental overhead, The Context Pattern would work best. You're probably good to use something like Zustand or Jotai.
This is my opinion on the patterns themselves and not the libraries. Libraries offer features that might make them a better choice than the others.
What about server state?
That's another beast altogether. In the next blog post/video, I plan to talk about the individual libraries, the features along with server state management. Do subscribe if you don't want to miss it.