Data Flow
Understanding how data flows through the Vertical Template helps you build features that integrate correctly with the architecture.
State Management Overview
The template uses a unified Zustand store created dynamically from the tenant configuration:
Store Hooks Reference
useStoreValue
Read a value at a dot-notation path (reactive):
import { useStoreValue } from '@enterpriseaigroup/client';
function PropertyDisplay() {
const address = useStoreValue('property.selectedAddress');
const stage = useStoreValue('stage.current');
return (
<div>
<p>Address: {address}</p>
<p>Stage: {stage}</p>
</div>
);
}
useSetStore
Get the setter function for updating store values:
import { useSetStore } from '@enterpriseaigroup/client';
function StageButton() {
const setStore = useSetStore();
const goToPlanning = () => {
// Third argument is caller name for DevTools
setStore('stage.current', 'planning', 'StageButton');
};
return <button onClick={goToPlanning}>Next</button>;
}
useGlobalSelector
Create a custom selector for derived state:
import { useGlobalSelector } from '@enterpriseaigroup/client';
function UserGreeting() {
const displayName = useGlobalSelector(
(state) => state.user?.name ?? 'Guest'
);
return <p>Hello, {displayName}</p>;
}
useStoreGetter
Get a non-reactive getter (for callbacks, avoids stale closures):
import { useStoreGetter } from '@enterpriseaigroup/client';
function ChatSubmit() {
const getMessages = useStoreGetter('chat.messages');
const handleSubmit = useCallback(() => {
// Always returns current value, no stale closure
const messages = getMessages();
console.log('Messages:', messages.length);
}, [getMessages]);
}
useResetSlice
Reset a slice to its initial state:
import { useResetSlice } from '@enterpriseaigroup/client';
function ResetButton() {
const resetProperty = useResetSlice('property');
return (
<button onClick={resetProperty}>
Clear Property Selection
</button>
);
}
useClearPersistedStore
Clear all persisted state (useful for logout):
import { useClearPersistedStore } from '@enterpriseaigroup/client';
function LogoutButton() {
const clearStore = useClearPersistedStore();
const handleLogout = () => {
clearStore();
// Redirect to login
};
}
Store Bindings
Components receive props from the store via storeBindings in config:
// In tenant config
{
component: 'PropertyInfoCard',
storeBindings: [
{ prop: 'address', storePath: 'property.selectedAddress' },
{ prop: 'propertyId', storePath: 'property.propertyId' },
],
props: {
mapConfig: { interactive: true },
},
}
The SlotRenderer automatically resolves bindings at render time, injecting store values as component props.
Visibility Conditions
Components can be conditionally shown based on state:
{
component: 'PlanningStage',
showWhen: (state) => state.stage?.current === 'planning',
}
The condition is evaluated on every state change.
Data Flow Example
1. User selects an address in the UI
└─► onClick handler fires
2. Component calls setStore
└─► setStore('property.selectedAddress', '123 Main St', 'AddressSearch')
3. Store updates via Immer
└─► Immutable state update + persist to sessionStorage if enabled
4. SlotRenderer detects state change
└─► Re-evaluates showWhen conditions
5. Components re-render with new props
└─► storeBindings inject updated values
API Data Flow
For data from the backend:
1. Component needs data
└─► useQuery or fetch call
2. Request goes to /api/eai/*
└─► fetch('/api/eai/v3/projects', { credentials: 'include' })
3. BFF proxy adds auth token
└─► Authorization: Bearer <token>
4. Backend returns data
└─► Response proxied back to client
5. Component updates store
└─► setStore('projects.list', data, 'ProjectsPage')
6. UI reflects new data
└─► Components using useStoreValue('projects.list') re-render
Persistence
Slices with persist: true are saved to sessionStorage:
store: {
property: {
initialState: { selectedAddress: null },
persist: true, // Survives page refresh
},
ui: {
initialState: { sidebarOpen: true },
persist: false, // Resets on refresh
},
}
- Survives page refresh within the same session
- Cleared on tab close (sessionStorage behavior)
- Slice-level control: Only persist what you need
DevTools Integration
Actions are named for debugging in Redux DevTools:
setStore('user.email', 'test@example.com', 'AddressLookup');
// In Redux DevTools: "set/user.email (AddressLookup)"
Enable the Redux DevTools browser extension to see all state changes.
Related Documentation
- Architecture Overview — System design
- Configuration Overview — Tenant configuration
- Hooks Reference — Complete hooks API
API & Hooks
- React Hooks: useResources — Typed resource CRUD hooks
- React Hooks: useChat — SSE streaming chat hook
- React Hooks: useDocuments — Document upload and management hook
- Platform SDK — TypeScript SDK client reference