This article is part of a blog series in which we look at back at five years of React usage at Instana.
State management is one of the essential aspects of React application architectures. Over the years, we certainly learned a lot about it. This blog post will provide insights into our current state management approaches.
Let’s begin with a controversial point first: We don’t use Redux! Redux did not yet exist when we started Instana. We don’t miss Redux, and there are mixed feelings about it at Instana, but this is a topic for another day. At Instana, we are differentiating between three kinds of state. Before we dive into these, let us take a step back to understand how we arrived at this design.
Early State Management Struggles
In the beginning, we tried to use some of the standard state management techniques of the time. These techniques eventually led down the path of stores, i.e., state keepers capable of state manipulation and propagation. Without going into too much detail or complaining about a specific library, we misused stores! We interpreted stores as the place to save every piece of state. In combination with global stores, this led to a complex store environment that became increasingly complicated to understand and reason about. New and infrequent contributors had a tough time. Even seasoned contributors were struggling to correctly model behavior using these global stores.
Once we revisited our approach, the guidelines changed: All new components henceforth used either URL state or React local state for what was previously “store state”—more on these in the following sections. This change in approach simplified development a lot.
URL state is a kind of state which has to be retained across page refreshes, bookmarks or as links in emails, social tools and reports. We are pedantic about URL state! Whether it is a filter, the sorting criteria for a table, the active time configuration or metrics you want to chart: They all go into the URL. We are frustrated when deep links cannot be shared, particularly when troubleshooting a production system outage. Our users seem to agree because they remind us whenever we accidentally use local state where URL state should have been used.
Technically it means that the single source of truth for the state is the URL, i.e.,
window.location.href. The state updates when the URL changes and then the DOM updates. It still is a uni-directional data flow in the end!
Proper URL state management comes with many challenges. For example:
- URL state cleanup on user navigation.
- Synchronous URL state updates on user inputs (URL updates are asynchronous).
- Modeling URL state dependencies.
- And more.
Even after about four years of URL state production usage, we are still finding the occasional bug. We plan to open-source our URL state management logic in the future as we believe that such a state management system can be beneficial for most React applications.
The most prominent and crucial task of the Instana user interface is information presentation. Be it alerts, issues, configuration data, metrics, traces, website activity or anything else: Most of the time, the UI translates the need for information into a technical data subscription and presents whatever it received. We call this transient state. Transient state comes and goes quickly, especially when users navigate within the user interface to their desired information. Each piece of state varies a lot in size: A metric time series, a single metric value or large JSON blobs for traces.
In the end, the existence of transient state is bound to mounted React components. Transient state is disposed of when components unmount. Since users sometimes go back and forth quickly, we do not dispose of the state immediately but keep them hanging for some time in case the user comes back. This delayed state disposal helps to avoid subscription thrashing. The technical details of this mechanism exceed the context of this blog post. Should you know ReactiveX: Imagine a shared reference-counted replay subject which delays unsubscribe signal propagation.
Local state is your regular old React component state in various technical flavors. Old components implement state via classes that extend from
React.Component. Newer components solely use React’s
useState hook. In between, there are cases where we use recompose’s
withState HOC. In the end, it all boils down to the same core idea: State that is local to a React sub-tree. We aim to eventually only use React’s
useState hook for local state.
We use local state for all cases that do not fit into either the URL state or transient state categories. Forms are the most common example in this category.
State management is at the core of any React application. The chosen approach can have a lasting impact on the structure and maintainability of a React application.
Stores and especially Redux/Recoil as concrete state containers, are prominent in this space and are frequently recommended. Looking back, we would now strongly advise you to start a project without them. Consider stores once you have requirements that indicate a need for such an approach, but not as a default.
The next article in this series will be released soon.