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