Specialized components
UI framework
State management (Hyperflux)
23 min
overview state management is a critical aspect of modern ui development, particularly for complex applications like the ir engine hyperflux is the state management system used throughout the ir engine ui framework to maintain data consistency and enable reactive updates across components it provides a centralized approach to managing both global application state and local component state, ensuring that ui elements automatically reflect the current data and remain synchronized this chapter explores how hyperflux works and how it's implemented in the ir engine ui framework core concepts state in ui development, "state" refers to any data that can change over time and affects what the user sees or how the application behaves examples include which item is currently selected whether a dropdown menu is expanded or collapsed what text is entered in an input field which user is currently logged in which entities are currently visible state management becomes increasingly important as applications grow in complexity, particularly when multiple components need access to the same data or when changes in one component need to be reflected in others hyperflux architecture hyperflux provides a structured approach to state management with several key components state definitions centralized declarations of state structures and their initial values state subscriptions mechanisms for components to "listen" for changes to specific state values state mutations methods for updating state in a controlled, predictable manner automatic updates system for notifying and re rendering components when relevant state changes this architecture creates a unidirectional data flow that makes applications more predictable and easier to debug sequencediagram participant component1 as ui component (writer) participant hyperflux as hyperflux state participant component2 as ui component (reader) component1 >>hyperflux update state (set) hyperflux >>hyperflux process state change hyperflux >>component2 notify of state change component2 >>component2 re render with new state implementation defining state state is defined using the definestate function, which creates a centralized repository for a specific slice of application data // from src/components/chat/chatstate ts import { definestate } from '@ir engine/hyperflux'; export const chatstate = definestate({ name 'ui chat chatstate', // unique identifier for debugging initial () => ({ // initial state values selectedchannelid null as string | null, istyping false, unreadmessages {} as record\<string, number> }) }); this creates a state container with a unique name for identification initial values for all state properties type definitions for proper typescript support state definitions can also include helper functions for common operations // from src/components/editor/componentdropdown/componentdropdownstate ts export const componentdropdownstate = definestate({ name 'componentdropdownstate', initial () => ({ componentstates {} as record\<entityuuid, record\<string, boolean>> }), // helper function to update component state addorupdateentity (entity, componentname, value) => { const state = getmutablestate(componentdropdownstate); const entityuuid = getentityuuid(entity); if (!state componentstates\[entityuuid]) { state componentstates\[entityuuid] = {}; } state componentstates\[entityuuid]\[componentname] = value; } }); reading state in components components can subscribe to state changes using the usehookstate hook // example component that displays the selected channel import react from 'react'; import { usehookstate, getmutablestate } from '@ir engine/hyperflux'; import { chatstate } from ' /chatstate'; function selectedchanneldisplay() { // subscribe to the selectedchannelid state const selectedchannelid = usehookstate(getmutablestate(chatstate) selectedchannelid); // component will automatically re render when selectedchannelid changes return ( \<div classname="channel header"> current channel {selectedchannelid value || 'none selected'} \</div> ); } key points usehookstate subscribes the component to changes in the specified state value accesses the current state value the component automatically re renders when the subscribed state changes updating state components can modify state using the usemutablestate hook // example component with buttons to change the selected channel import react from 'react'; import { usemutablestate } from '@ir engine/hyperflux'; import { button } from '@ir engine/ui'; import { chatstate } from ' /chatstate'; function channelswitcher() { // get a mutable reference to the chatstate const chatstate = usemutablestate(chatstate); const handleselectchannel = (channelid string) => { // update the selectedchannelid chatstate selectedchannelid set(channelid); }; return ( \<div classname="channel switcher"> \<button onclick={() => handleselectchannel('general')}> general \</button> \<button onclick={() => handleselectchannel('random')}> random \</button> \</div> ); } key points usemutablestate provides access to modify the state set() method updates a specific state property changes are immediately reflected in all components subscribed to that state non component state access for accessing state outside of react components (in utility functions, services, etc ), hyperflux provides the getmutablestate function // example utility function that works with state import { getmutablestate } from '@ir engine/hyperflux'; import { chatstate } from ' /chatstate'; export function markchannelasread(channelid string) { const chatstate = getmutablestate(chatstate); // update unread message count for the channel const unreadmessages = { chatstate unreadmessages value }; unreadmessages\[channelid] = 0; chatstate unreadmessages set(unreadmessages); console log(`marked channel ${channelid} as read`); } this allows state to be accessed and modified from anywhere in the application, not just within react components practical examples component dropdown state the componentdropdownstate manages which property sections are expanded or collapsed in the editor // from src/components/editor/componentdropdown/componentdropdownstate ts import { definestate, getmutablestate } from '@ir engine/hyperflux'; import { getentityuuid } from '@ir engine/ecs'; export const componentdropdownstate = definestate({ name 'componentdropdownstate', initial () => ({ componentstates {} }) }); // in the componentdropdown component function componentdropdown({ entity, componentname, defaultexpanded = true }) { const entityuuid = getentityuuid(entity); // get the expanded state from global state or use default const isexpanded = usehookstate( getmutablestate(componentdropdownstate) componentstates\[entityuuid]? \[componentname] ?? defaultexpanded ); const toggleexpanded = () => { const state = getmutablestate(componentdropdownstate); if (!state componentstates\[entityuuid]) { state componentstates\[entityuuid] = {}; } state componentstates\[entityuuid]\[componentname] = !isexpanded value; }; return ( \<div classname="component dropdown"> \<div classname="header" onclick={toggleexpanded}> {componentname} {isexpanded value ? '▼' '►'} \</div> {isexpanded value && ( \<div classname="content"> {/ component content /} \</div> )} \</div> ); } this example shows how hyperflux can manage ui state that persists across component instances, allowing the editor to remember which sections are expanded chat feature state the chat feature uses hyperflux to coordinate between multiple components // in channelslist tsx function channelslist() { const chatstate = usemutablestate(chatstate); const selectedchannelid = usehookstate(getmutablestate(chatstate) selectedchannelid); return ( \<div classname="channels list"> {channels map(channel => ( \<div key={channel id} classname={`channel ${selectedchannelid value === channel id ? 'selected' ''}`} onclick={() => chatstate selectedchannelid set(channel id)} \> {channel name} \</div> ))} \</div> ); } // in messagecontainer tsx function messagecontainer() { const selectedchannelid = usehookstate(getmutablestate(chatstate) selectedchannelid); // fetch messages for the selected channel const messages = usemessages(selectedchannelid value); return ( \<div classname="message container"> {selectedchannelid value ? ( <> \<div classname="messages"> {messages map(message => ( \<div key={message id} classname="message"> {message text} \</div> ))} \</div> \<messageinput channelid={selectedchannelid value} /> \</> ) ( \<div classname="no channel">select a channel to start chatting\</div> )} \</div> ); } this example demonstrates how hyperflux enables different components to stay synchronized without direct communication between them state update workflow when state changes occur, hyperflux follows this workflow sequencediagram participant user participant channelswitcher as channelswitcher component participant hyperflux as hyperflux system participant chatstateobj as chatstate (in hyperflux) participant displaycomponent as selectedchanneldisplay component user >>channelswitcher clicks "general" button channelswitcher >>hyperflux chatstate selectedchannelid set("general") hyperflux >>chatstateobj updates selectedchannelid to "general" chatstateobj >>hyperflux confirms update hyperflux >>displaycomponent notifies of state change displaycomponent >>displaycomponent re renders with new value displaycomponent >>user shows "current channel general" this unidirectional flow ensures predictable updates and makes debugging easier key hyperflux functions definestate definestate({ name string, initial () => stateshape, additionalhelpers }) creates a new state definition with name a unique identifier for debugging initial a function that returns the initial state values optional helper methods for common operations on this state usehookstate usehookstate(statevalue) a react hook that subscribes a component to changes in the specified state value returns a state object with value for reading and set() for writing causes the component to re render when the state changes usemutablestate usemutablestate(statedefinition) a react hook that provides a mutable reference to an entire state definition allows components to modify state without necessarily subscribing to changes is typically used for writing to state rather than reading getmutablestate getmutablestate(statedefinition) a function that can be used anywhere, not just in react components provides direct access to read or write state is useful in utility functions, services, or helper methods next steps with an understanding of how state is managed throughout the ui framework, the next chapter explores how components are documented, tested, and showcased using storybook integration next storybook integration docid\ jvvhg3ns1u9dsrb8qmorm