Managing state is a hard problem. We need to coordinate multiple backends, web workers, and UI components, all of which update the state concurrently. Patterns like Redux make some of this coordination explicit, but they don’t solve the problem completely. It is much broader.
What should we store in memory and what in the URL? What about the local UI state? How do we synchronize the persistent state, the URL, and the state on the server? All these questions have to be answered when designing the state management of our applications.
Types of State
A typical web application has the following six types of state:
- Server state
- Persistent state
- The URL and router state
- Client state
- Transient client state
- Local UI state
The server state is stored, unsurprisingly, on the server and is provided via, for example, a REST endpoint. The persistent state is a subset of the server state stored on the client, in memory. Naively, we can treat the persistent state as a cache of the server state. In real applications though this doesn’t work as we often want to apply optimistic updates to provide a better user experience.
The client state is not stored on the server. A good example is the filters used to create a list of items displayed to the user. The items themselves are stored in some database on the server, but the values of the filters are not. It’s a good practice to reflect both the persistent and client state in the URL.
Applications often have state that is stored on the client, but is not represented in the URL. For instance, YouTube remembers where I stopped the video. So next time I start watching it, it will resume the video from that moment. Since this information is not stored in the URL, if I pass a link to someone else, they will start watching from the beginning. This is transient client state.
Finally, individual components can have local state governing small aspects of their behavior. Should a zippy be expanded? What should be the color of a button? This is local UI state.
When identifying the type of state, ask yourself the following questions: Can it be shared? What is its lifetime.
The persistent state and the server state store the same information. So do the client state and the URL. Because of this we have to synchronize them. And the choice of the synchronization strategy is one of the most important decisions we make when designing the state management of our applications.
Can we make some of this synchronization synchronous? What has to be asynchronous? Or using the distributed systems terminology: should we use strict or eventual consistency?
Let’s see what manages each type of state.
- Backend manages the persistent state (the talks) and the client state (the filters).
- The router manages the URL and the router state.
- WatchService manages the transient client state (watched talks).
- The individual components manage the local UI state.