State Management

State management is at the heart of Ignite-Element’s design. By integrating with state management libraries like XState, Redux, or MobX, Ignite-Element lets you decouple your component’s logic from its rendering, resulting in simple, reusable, and framework-agnostic components.


Decoupling Logic from Rendering

A key pattern to follow when using Ignite-Element is to keep all application logic—state transitions, actions, and business rules—inside the state management library. Ignite-Element focuses solely on rendering the component based on the state and events provided by the library.

This separation of concerns offers several benefits:

  • Dumb Components: Components become "dumb" or purely presentational, only concerned with rendering the current state and dispatching events.

  • Reusability: As long as the state management library handles the required state and actions, the same component can be reused across multiple frameworks or projects.

  • Maintainability: Logic and rendering are clearly separated, making the codebase easier to understand and maintain.


Example: Traffic Light

State Management (XState Example)

The traffic light’s state machine is defined:

  • States for each light (red, yellow, green).

  • Transitions between states on a NEXT event.

  • A timer for automatically transitioning after a delay.

import { createMachine } from "xstate";

const trafficLightMachine = createMachine({
  id: "trafficLight",
  initial: "red",
  states: {
    red: {
      after: { 5000: "green" }, // Transition to green after 5 seconds
      on: { NEXT: "green" },
    },
    green: {
      after: { 5000: "yellow" }, // Transition to yellow after 5 seconds
      on: { NEXT: "yellow" },
    },
    yellow: {
      after: { 3000: "red" }, // Transition to red after 3 seconds
      on: { NEXT: "red" },
    },
  },
});

export default trafficLightMachine;

Component Rendering

Ignite-Element components use state.value to render the appropriate UI dynamically. This ensures the rendering logic is simple, scalable, and directly tied to the state machine.

import { igniteCore } from "ignite-element";
import { html } from "lit-html"
import trafficLightMachine from "./trafficLightMachine";

const { shared } = igniteCore({
  adapter: "xstate",
  source: trafficLightMachine,
});

shared("traffic-light", (state, send) => {
  // Use state.value to set the color dynamically
  const color = state.value;

  return html`
    <div style="text-align: center;">
      <div
        style="height: 100px; width: 100px; margin: auto; border: 1px solid black; border-radius: 50%; background: ${color};"
      ></div>
      <button @click=${() => send({ type: "NEXT" })} style="margin-top: 20px;">
        Next
      </button>
    </div>
  `;
});

Why This Pattern Works

By keeping the logic in the state machine and using Ignite-Element purely for rendering:

  • Components are framework-agnostic and easy to reuse.

  • Logic is centralized in the state machine, making it easier to test and debug.

  • The components remain "dumb," focused only on displaying the current state and triggering events.


Best Practices

  • Centralize Logic in the State Machine: Use state management libraries to define all state transitions, actions, and context.

  • Simplify Component Rendering: Use state.value for conditional rendering to keep components readable and maintainable.

  • Ensure Reusability: You can reuse the same components in different projects or frameworks by decoupling logic from components.

Last updated