State Management for User Interfaces
State management is sometimes considered to be one of the most complex aspects of software development. Developers often result to complicated solutions when a core principle of a problem is not entirely understood. At a glance, states change appear to be a straight forward topic to dissect but it’s not. Though with a little patience, wisdom and planning you can manage state gracefully without introducing chaos via any platform, language or framework.
Before we dive into this topic I want to warn you that this article dives into a couple of niche topics so there are a bunch of made-up terms thrown around that I couldn’t avoid as I was not able to come across any publications regarding the quirks of those topics especially with regards to user-interfaces.
As you may know, buggy behaviour is common across a number of software platforms. Consider three major culprits to be:
- Humans: Poor project management /pressure/ inexperienced developers.
- Implementation: Poor methodologies/ convoluted library APIs.
- Unnatural UI Design: Designs that expect components to instantly disappear and or reappear.
The last is problematic and sadly the norm especially for web design. If a component is destroyed and after recreated whilst containing other stateful elements, you may need to manage those states as well as states of nested elements not to mention the states of parent’s interfaces in conjunction to the aforementioned. This can cause edge cases to accumulate, not always with pretty little errors and warnings.
Rather than managing state in a binary fashion, interactive designs should favour transitions (i.e. alpha fades/ translations) and other methods where UI elements are retained in memory and perceived as a physical part of the interface on and off screen.
Frame based UI designs have passed their life expectancy. There’s a hidden cost to making things disappear and reappear. The debt is usually left with state management.
The core of this article is actually about the theory of state machine features. The intro was just a tap dance around the bigger picture as this subject goes far deeper than programming.
What is state?
(In this article “object” predominantly refers to “An entity of some kind” not an object literal per se)
State is a condition of an object stored as data. Therefore:
CCTV is state management
CCTV systems records events for a specific purpose. Each frame is a story of captured state/s. Elements within each frame are not the actual state but stateful objects which we refer to as stateObjects.
There is no such thing as “nested state” that’s not how state works. State is linear to time.
With that digestible paradigm in mind, here are some fundamental aspects of state management to consider when building state management libraries for production or as open source projects.
All state management libraries Should! Include:
- stateObjects as entities: A stateObject is simply a context-object. This alludes to stateObjects being represented as portable entities/ interfaces in your code that can be handle and manipulate rather than working invisibly with a store or stores. i.e. an object (programming term) , class or function.
- Transition journey: A stateObject’s interface must inform the developer if the stateObject is transitioning from state A to state B or from F to C. Especially for pre-defined states. (Use state A vs state B rather than Backwards vs forward, as direction creates convoluted abstractions. Let the dev deal with the aspect of direction conditionally).
- Mechanisms (Mechanical effects): The most common analogy would be the domino effect. If an action is inevitable by the stateChange of a stateObject then that actionable should not be a state object. It should be regarded as a mechanical process. (yes fancy words but that correlate directly to the physical world). The emphasis here is: Everything that changes state does not need to be managed for the sake of feeling in control. Mechanisms should also be reversible (see below) if your state machine is persistent. The purpose of distributing stateChange automatically is to reduce complexities.
Additionally for large code bases and complex applications:
- Hierarchical categories: This is ideal for tractability but not mandatory. You can shortcut conditions by tracing the state of stateObjects from parent and sibling categories. E.g. (Check if all stateObjects in category x are currently false and by using minimal code). This is far better than nesting state (which is a dumb-ass concept never do it). With that last comment said it’s important to stress that categories are not stateObjects. Consider them as nodes that group stateObjects into trees.
- Always use persistence: Even when you think you don’t need to go back in time or undo, ensure your state management library is persistent ready. You don’t need to record everything, you should limit the tail of persistence as needed. Lack of persistence causes developers to manage short lived persisted states as if they were current (= more complexities, more edge case issues) which is bad practice because I said so!
- Reversible functions: As mentioned, this concept expects an actionableCallback to perform both progressive and regressive actions for a given transitionJourney and or timeStamp. This allows the UI to gracefully step back or forth within history. Building actionableCallbacks to work forwards and backwards in time will answer your question to why stateObjects should be represented individually rather than as a set of store methods or reducers (i.e. Redux).
Here’s some more digestible common sense for various edge-cases: Debounce. Use debounce functionality to prevent undesirable user events that may trigger unhinged outcomes. But don’t debounce everything.
Your state management should be time based. Since computers are stupidly fast today I don’t believe there will be many cases where the actual state machine you are designing should “need” to expose an async interface. In most cases this can be handled with more control externally.
But what about…
Notice I’m not talking about futures, promises, reducers, dispatchers, middleware, et cetera. Because there’s a difference between real world problems and fantasy problems.
Here’s some terms that I use to help refer to various aspects revolving around state management.
- stateObject: UI perspective = A UI that needs it’s state managed. Code perspective = Acontext object with methods that represents the state.
- frame: A snapshot within the time-line chain that contains stateChange/s
- journey: A description of stateChanges for a given stateObject: A -> C -> B
- actionableCallback |actionable: The callback of a stateObject
- Mechanism: A function triggered via a stateObject with consequential and inevitable state changes that are not recorded by the store.
- category: A node that groups stateObjects
- persistence: The accumulation of stateChange throughout time
- node: You already node what a node is.
- reversibleFunction: A type of actionableCallback that can action on persisting and regressing stateChanges.
- timeStamp: When stateChange occured.
- debounce: It’s like throttle but not ;-)
- Unnatural Design: An interactive UI design that revolves around elements being destroyed and created in areas where this can and should be avoided.
- Natural Design: An interactive UI design that emphasises the minimal requisite for created and destroyed elements.
Opinions are welcomed, whatever they may be.
Thanks for the read ;-)