Brad Woods Digital Garden

Notes / JavaScript / State management

The Warhammer 40k Adeptus Mechanicus symbol

Table of contents

    A number of boxes connect by lines with arrows

    State Management

    Planted: 

    Tended: 

    Status: decay

    Hits: 186

    A bunch of stickers with 'Hello, my name is...' written on them

    Named States

    A common approach in React is to use boolean flags to keep track of state. For example, imagine we made a component that fetched an image. It could look like the following:

    MyImageFetcher.tsx

    const MyImageFetcher = () => {
    const [isFetching, setFetching] = useState(false);
    const [isSuccess, setSuccess] = useState(false);
    const [isError, setError] = useState(false);
    ...

    A disadvantage of this is it results in untended states. The intended amount of states for the component is 4; idle, fetching, success and error. The actual number of states produced from the above approach is 23 = 8. Changing the approach from boolean flags to named states can fix this:

    MyImageFetcher.tsx

    type State = 'IDLE' | 'FETCHING' | 'SUCCESS' | 'ERROR'
    const MyImageFetcher = () => {
    const [state, setState] = useState('IDLE');
    ...

    Intended number of states = 4. Actual number of states = 4.

    2 cogs

    Finite-State Machines

    When state management requirements become complex, finite-state machines are the best approach to UI state management.

    React devs: if your app logic is super simple and only requires fetching + displaying data (CRUD), the only state management you really need is React Query + *maybe* useState. Consider state management libraries when the logic gets more exciting than that. The second your app needs shared state, multi-step user flows, stateful logic, advanced user interactions, etc., your app graduates from "simple" to "complex"
    David K. PianoFounder of Stately
    Portrait of Spock the Vulcan

    Advantages

    • Disable all user actions by default: Within each state, you have a centralized area where you define what events can occur, reducing edge cases.
    • Seperation of concerns: Logic can be seperated from everything else. This allows logic to be reused when changing frameworks (such as moving from Angular to React), easier seperation of work between team members and easier testing / debugging.
    • Named states: is core to the structure (see above why this is an advantage).
    • Scalability: Statecharts scale well as complexity grows. You can easily manage distributed behavior through the actor model.
    • Visualization: Logic can be visualized as a diagram (known as a statechart), acting as accurate documentation, making it easier to understand a system and acting as a communication language between coder / non-coder stakeholders.
    • Exploration: The process of building a statechart encourages all states to be explored. This can prevent edge cases from being overlooked.
    • Battle proven: 30+ years of refinement.
    • Formalised logic: increasing a codebase's consistency.

    Disadvantages

    • Steep learning curve: State machines are a significantly different way of thinking about state when compared to popular state management libraries such as Redux.
    • Over-engineering: A state machine may seem like going overboard when a small amount of state management is required, such as a toggle that only requires a "on" and "off" state. This can be achieved with a small amount of code using React's "useState" hook. The counter-argument to this is, often what begins as simple state grows in complexity. State machines may be more work to begin with but less work at scale.

    Currently, XState is the most popular JavaScript finite-state machine library.

    Naming Conventions

    • Events: when naming events performed by the user, accuratley describe what happened, not the intention of the user action. Example, when a user clicks a sign in button on a form, instead of signIn, name the event signInButtonClicked.
    • Context: when naming an action that updates a machine's context, prefix the action with assign. Example, when an action changes context.email, name the action assignEmail.

    Storing State

    Storing state from a previous session and restoring it can improve the UX (User Experience). Different approaches to storing state:

    localStorage

    Although any state stored here will not be available on different devices, it can be useful for websites that don't require sign-in (and therefore can't send state to be stored in a database). Example, a Real Estate website that shows houses available to purchase. If it had a min / max price filter, the values set could be stored in localStorage and retrieved / applied each time the user returns to the website.

    sessionStorage

    Similar to local storage except anything stored here will be cleared when the page session ends. Survives page reloads and restores.

    Query Params

    Key / values stored in a URL. Useful when there is a requirement for a user to send a page address to another user in its current state.

    Database

    The most feature rich and expensive storage approach.

    Feedback

    Have any feedback about this note or just want to comment on the state of the economy?

    Where to next?

    JavaScript
    Arrow pointing downYOU ARE HERE
    A Johnny Cab pilot