Determine your state's scope
Welcome, to the fifth part of the "Effective Application State Management"-series. In the previous lesson I pointed out the advantages of immutable states. In this article I'd like to emphasize that it's sometimes wiser to handle a state inside a component rather than on application level.
Nearly one year ago, I wrote an article with the baiting title Using Flux for component states is an anti-pattern! It was a kind of an eye-opener, when I started to introduce my very own Flux implementation NanoFlux in a project I was working on. I refactored a Suggestion (aka Auto-completion) component trying to introduce Flux. The initial idea was to trigger an action on input changes, that hits the server to get the suggested items, which then were dispatched towards a store. The store finally notifies listening components about the items arrival. I realized that using Flux for that case was not a good idea, because I needed to keep track of the requesting components, because it's very likely that a form/screen contains more than one suggestion. But React (and so do other component-based frameworks/libraries) already resolves it for me implicitly. So, I reinvented the wheel and added a bunch of unnecessary complexity. Additionally, this approach has some - although minor - performance implications, as Flux always broadcasts state changes to its listeners. But the worst of all is, that the component is not reusable anymore as I added a dependency to NanoFlux. In the less evil case it just adds a bunch of mostly useless code, but could also generate severe conflicts. Funny enough, that a few months later, I needed a Suggestion component again, and I started to search for existing components. I discovered one at Github with more than thousand stargazers, but when I looked at the implementation I was surprised that the author has committed the same mistake...he used Redux to maintain the items. Actually, some users complained conflicts with other versions of Redux.
The author refactored his component and removed Redux.
I learned two things from this experience:
- It's not only me who made/makes the mistake
- One need to decide what is application state, and what is component state.
How to determine, if a state is a component or application state?
I recommend to start simple, i.e. create your component normally. Once, you need to introduce a state, you should evaluate if that state is an internal implementation detail, or is somehow exposed. One indicator for an internal/component state can be the fact, that the state won't be used by any other component. This might not be clear at that moment, so in case of doubt keep it internal. Another indicator for component state is the tight coupling to an instance of that component. Ask yourself: "If that state would be external, do I need to track the related instance?" If you answer this question with yes, it's very likely that the state is internal. In the case of the Suggestion component the item list is tightly coupled to the instance, that's why instance tracking was necessary.
An indicator for application state are different lifetimes of a state and its related component. Imagine a data grid with filters and pagination. It'd be much more comfortable if the grid's filter and pagination settings aren't reset each time the grid reloads. So, you could push the grid state to application level, such that each re-rendering of the data grid returns to the previous settings.
Of course, in the data grid case the state can be persisted in different ways, e.g. localStorage or even on server.
Additionally, I think it's worth mention that you should always seek to make (especially reusable) components as stateless as possible. A common pattern is the separation in container and presentational components. That way, it's a bit easier to reason about component and application states, and sometimes it turns out that states in the container component doesn't have to be extracted towards application level.
You should consider to keep a state within a component when
- state is not shared among other components
- state is tightly coupled to the a component, i.e. using an Id to define the relation.
You should consider introduce an application state, when
- state is shared among (unrelated) components
- state shall be maintained independent of a components lifetime