Outcome Recipes
Advanced Store patterns for async workflows, testing, form management, selectors, and type-safe state management with @aurelia/state.
1. Async action workflows with loading states
Steps
interface User { id: string; name: string; email: string; } interface AppState { users: { data: User[]; loading: boolean; error: string | null; }; currentUser: { data: User | null; loading: boolean; error: string | null; }; } const initialState: AppState = { users: { data: [], loading: false, error: null }, currentUser: { data: null, loading: false, error: null } };import { IHttpClient } from '@aurelia/fetch-client'; import { resolve } from '@aurelia/kernel'; // Request action - sets loading state export const fetchUsersRequest = (state: AppState) => ({ ...state, users: { ...state.users, loading: true, error: null } }); // Success action - stores data export const fetchUsersSuccess = (state: AppState, users: User[]) => ({ ...state, users: { data: users, loading: false, error: null } }); // Failure action - stores error export const fetchUsersFailure = (state: AppState, error: string) => ({ ...state, users: { ...state.users, loading: false, error } }); // Async thunk - orchestrates the flow export async function fetchUsers(state: AppState): Promise<AppState> { const http = resolve(IHttpClient); // Start loading let newState = fetchUsersRequest(state); try { const response = await http.fetch('/api/users'); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const users = await response.json(); // Success return fetchUsersSuccess(newState, users); } catch (error) { // Failure return fetchUsersFailure(newState, error.message); } }import Aurelia from 'aurelia'; import { StateDefaultConfiguration } from '@aurelia/state'; Aurelia .register( StateDefaultConfiguration.init( initialState, {}, fetchUsersRequest, fetchUsersSuccess, fetchUsersFailure, fetchUsers ) ) .app(MyApp) .start();import { IStore } from '@aurelia/state'; import { resolve } from '@aurelia/kernel'; import { fromState } from '@aurelia/state'; export class UserList { private store = resolve(IStore<AppState>); @fromState(s => s.users.data) users: User[]; @fromState(s => s.users.loading) loading: boolean; @fromState(s => s.users.error) error: string | null; async attached() { await this.store.dispatch(fetchUsers); } async refresh() { await this.store.dispatch(fetchUsers); } }<div class="user-list"> <div if.bind="loading" class="loading"> <span>Loading users...</span> </div> <div if.bind="error" class="error"> <p>${error}</p> <button click.trigger="refresh()">Retry</button> </div> <div if.bind="!loading && !error"> <div repeat.for="user of users" class="user-card"> <h3>${user.name}</h3> <p>${user.email}</p> </div> <div if.bind="users.length === 0" class="empty"> No users found. </div> </div> </div>
Checklist
2. Form state management with validation
Steps
Checklist
3. Memoized selectors for performance
Steps
Checklist
4. Testing store actions and middleware
Steps
Checklist
5. Type-safe actions with discriminated unions
Steps
Checklist
6. Batch updates to prevent intermediate renders
Steps
Checklist
Store pattern cheat sheet
Pattern
Key API
Use When
Best practices
See also
Last updated
Was this helpful?