Aircada React Hooks for State Synchronization
React overlays observe state updates and patch adjustments back to the Vault. Rather than subscribing directly to the store, components must use these data-bound hooks to read and update Aircada configurations dynamically.
useAirStore React Hook#
Use the useAirStore hook to read application state configurations and patch values back to the global Vault.
Key Constraints
1. Never Spread State in Patches: Do NOT spread existing state inside a patch (e.g. patch({ ...state, open: true })). This creates stale closures and overrides viewport calculations. Specify only the exact nested fields to update, and let the Vault handle deep-merging. Refer to the Twin-Proxy store details for underlying merge mechanics.
2. State Configuration Typing: Always provide the generic store config type parameter (e.g. useAirStore<AirStoreConfig>()) to ensure key autocompletion.
import { useAirStore } from '@aircada/air-react';
import { AirStoreConfig } from './store/store.config';
export function CartPanel() {
const [storeState, patch] = useAirStore<AirStoreConfig>();
const toggleCart = () => {
// 🟢 CORRECT: Deep partial patch containing only the target key
patch({ ui: { cartOpen: !storeState.ui.cartOpen } });
};
return (
<button onClick={toggleCart} className="pointer-events-auto">
{storeState.ui.cartOpen ? 'Close Cart' : 'Open Cart'}
</button>
);
}useAirOptions React Hook#
The useAirOptions hook is the standard, data-bound hook for option selectors. It reads configuration selections, loads list details, and applies changes directly through Twin-Proxy paths.
Key Constraints
1. Never Call useAirStore for Options: Do not manually subscribe or patch option models using low-level store hooks. Let useAirOptions manage it.
2. No Raw path Strings: Never pass a raw string (e.g. 'bodyColor') to the hook. Pass the entire optionSet object imported from the auto-generated OptionsRegistry.
import { useAirOptions } from '@aircada/air-react';
import { OptionsRegistry } from '../registry/options.generated';
export function ColorPicker() {
// Bind to the option set object directly
const { options, isLoading, activeSelection, selectOption } = useAirOptions(
OptionsRegistry.BALL_O_STEEL.sets.SIMPLECOLORS
);
if (isLoading) return <div>Loading...</div>;
return (
<div className="flex gap-2 pointer-events-auto">
{options.map(row => (
<button
key={row.id}
className={activeSelection?.id === row.id ? 'border-cyan-500' : 'border-gray-500'}
onClick={() => selectOption(row)}
>
{row.name}
</button>
))}
</div>
);
}useAirDataset React Hook#
Use the useAirDataset hook when a component only needs to fetch lists of items (e.g. menu contents, layout descriptors) and doesn't need active Option selection tracking.
Key Constraints
1. Single Source of Truth: Never hardcode arrays or configurations that could live in a cloud spreadsheet. Maintain them in datasets.
2. Strong Typing: Always pass the generic row type parameter to the hook. Import row interfaces directly from /registry/ files.
import { useAirDataset } from '@aircada/air-react';
import { MediaRegistry, MenuDataRow } from '../registry/media.generated';
export function SidebarMenu() {
// Strongly typed row fetching
const { rows, isLoading } = useAirDataset<MenuDataRow>(
MediaRegistry.DATASETS.MAIN_MENU.id
);
if (isLoading) return <div>Loading...</div>;
return (
<ul>
{rows.map(row => (
<li key={row.id}>{row.Label}</li>
))}
</ul>
);
}useAirEvent React Hook#
Use the useAirEvent hook to trigger transient, fire-and-forget commands (such as resetting camera states or initiating checkout animations) that do not belong in persistent option states.
Key Constraints
1. No Magic Strings: All event names must be referenced from variables imported from registry.generated.ts.
2. Strict Payload Typing: Bind the event payload type parameter explicitly to prevent transmitting malformed structures.
import { useAirEvent } from '@aircada/air-react';
import { Events, EventPayloads } from '../registry/registry.generated';
export function ResetButton() {
// Strongly typed event invocation
const resetCameraEvent = useAirEvent<EventPayloads[typeof Events.RESET_CAMERA]>(
Events.RESET_CAMERA
);
const handleReset = () => {
resetCameraEvent.invoke({ reason: 'User requested reset' });
};
return <button onClick={handleReset}>Reset Camera</button>;
}





