May 20, 2026

Escaping the Boolean Soup: Managing Complex React UI with XState

By Paresh Prajapati • Lead Architect

Escaping the Boolean Soup: Managing Complex React UI with XState

The Nightmare of Multiple Booleans

When building data-fetching components in React, developers usually start simple: const [isLoading, setIsLoading] = useState(false). But as the B2B SaaS feature grows, the state becomes a fragile house of cards.

Suddenly, your component looks like this:
const [isLoading, setIsLoading] = useState(false)
const [isError, setIsError] = useState(false)
const [isSuccess, setIsSuccess] = useState(false)
const [isRetrying, setIsRetrying] = useState(false)

This is known as Boolean Soup. Mathematically, four booleans can exist in 16 different combinations. Is it possible for a component to be isLoading: true AND isError: true AND isSuccess: true at the exact same time? In standard React, yes. This leads to impossible UI states, flashing error messages during loading screens, and incredibly brittle useEffect logic.

The Solution: Finite State Machines (FSM)

To architect predictable UI, a component should only ever be in one exact state at a time. It is either 'idle', 'loading', 'success', or 'error'. It cannot be two things at once. We enforce this strict architecture using Finite State Machines, powered by a library called XState.

Architecting an XState Machine

Let's build a robust data-fetching machine. Instead of mutating booleans, we define strict states and the "transitions" allowed between them.


// machines/fetchMachine.ts
import { createMachine } from 'xstate';

export const fetchMachine = createMachine({
    id: 'dataFetcher',
    // 1. The machine STARTS in the 'idle' state
    initial: 'idle',
    states: {
        idle: {
            // From 'idle', the only valid action is 'FETCH', which moves it to 'loading'
            on: { FETCH: 'loading' }
        },
        loading: {
            // From 'loading', it can either succeed or fail
            on: {
                RESOLVE: 'success',
                REJECT: 'error'
            }
        },
        success: {
            // A terminal state. Or we could allow 'REFRESH' to go back to 'loading'
            on: { REFRESH: 'loading' }
        },
        error: {
            // From 'error', the user can hit 'RETRY'
            on: { RETRY: 'loading' }
        }
    }
});

Consuming the Machine in React

Using the @xstate/react hook, we bind our component to the machine. Notice how clean the rendering logic becomes. We do not check combinations of variables; we simply check the exact value of state.value.


// components/SecureDataPanel.tsx
"use client";

import { useMachine } from '@xstate/react';
import { fetchMachine } from '@/machines/fetchMachine';
import { useEffect } from 'react';

export default function SecureDataPanel() {
    const [state, send] = useMachine(fetchMachine);

    const fetchData = async () => {
        send({ type: 'FETCH' }); // Transition to 'loading'
        try {
            // Simulate API call
            await new Promise(res => setTimeout(res, 1500));
            send({ type: 'RESOLVE' }); // Transition to 'success'
        } catch (err) {
            send({ type: 'REJECT' }); // Transition to 'error'
        }
    };

    return (
        <div className="p-6 border rounded-lg shadow">
            {/* The UI strictly maps to the exact state machine value */}
            
            {state.matches('idle') && (
                <button onClick={fetchData} className="btn-primary">Load Dashboard Data</button>
            )}

            {state.matches('loading') && (
                <div className="animate-pulse">Loading secure data...</div>
            )}

            {state.matches('error') && (
                <div className="text-red-500">
                    <p>Connection failed.</p>
                    <button onClick={() => send({ type: 'RETRY' })} className="btn-outline">Try Again</button>
                </div>
            )}

            {state.matches('success') && (
                <div className="text-green-600">
                    <h3>Data loaded successfully!</h3>
                    {/* Render your charts here */}
                </div>
            )}
        </div>
    );
}

The Engineering ROI

State machines completely eliminate "impossible states." You never have to write complex validation logic to check if a user is trying to submit a form that is already in a loading state; the machine simply ignores the "SUBMIT" event if it is currently in the "loading" state. It forces developers to map out the entire lifecycle of a component before writing the UI, resulting in bulletproof, enterprise-grade React applications.

Paresh Prajapati
Lead Architect, Smart Tech Devs