State Machine Example
State machine pattern examples including complex state transitions, guard conditions, and action handling.
Basic State Machine
tsx
import { state } from 'flexium/core'
type LoadingState = 'idle' | 'loading' | 'success' | 'error'
function DataLoader() {
const [currentState, setCurrentState] = state<LoadingState>('idle')
const [data, setData] = state<any>(null)
const [error, setError] = state<Error | null>(null)
const loadData = async () => {
// idle -> loading
setCurrentState('loading')
setError(null)
try {
const res = await fetch('/api/data')
if (!res.ok) throw new Error('Failed to fetch')
const data = await res.json()
setData(data)
// loading -> success
setCurrentState('success')
} catch (err) {
setError(err as Error)
// loading -> error
setCurrentState('error')
}
}
const reset = () => {
setCurrentState('idle')
setData(null)
setError(null)
}
return (
<div>
{currentState === 'idle' && (
<button onclick={loadData}>Load Data</button>
)}
{currentState === 'loading' && (
<div>Loading...</div>
)}
{currentState === 'success' && (
<div>
<pre>{JSON.stringify(data, null, 2)}</pre>
<button onclick={reset}>Reset</button>
</div>
)}
{currentState === 'error' && (
<div>
<p>Error: {error?.message}</p>
<button onclick={loadData}>Retry</button>
<button onclick={reset}>Reset</button>
</div>
)}
</div>
)
}Complex State Machine (Form Submission)
tsx
import { state, sync } from 'flexium/core'
type FormState =
| { type: 'idle' }
| { type: 'validating' }
| { type: 'submitting' }
| { type: 'success'; data: any }
| { type: 'error'; message: string }
function ComplexForm() {
const [formState, setFormState] = state<FormState>({ type: 'idle' })
const [formData, setFormData] = state({
email: '',
password: ''
})
const validateForm = (data: typeof formData): string[] => {
const errors: string[] = []
if (!data.email) errors.push('Email is required')
if (!data.password) errors.push('Password is required')
return errors
}
const handleSubmit = async () => {
// idle -> validating
setFormState({ type: 'validating' })
const errors = validateForm(formData)
if (errors.length > 0) {
// validating -> error
setFormState({ type: 'error', message: errors[0] })
return
}
// validating -> submitting
setFormState({ type: 'submitting' })
try {
const res = await fetch('/api/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
})
if (!res.ok) throw new Error('Submission failed')
const data = await res.json()
// submitting -> success
setFormState({ type: 'success', data })
} catch (error) {
// submitting -> error
setFormState({
type: 'error',
message: (error as Error).message
})
}
}
const reset = () => {
setFormState({ type: 'idle' })
setFormData({ email: '', password: '' })
}
return (
<div>
{formState.type === 'idle' && (
<form onsubmit={(e) => { e.preventDefault(); handleSubmit() }}>
<input
value={formData.email}
oninput={(e) => setFormData({ ...formData, email: e.currentTarget.value })}
placeholder="Email"
/>
<input
type="password"
value={formData.password}
oninput={(e) => setFormData({ ...formData, password: e.currentTarget.value })}
placeholder="Password"
/>
<button type="submit">Submit</button>
</form>
)}
{formState.type === 'validating' && (
<div>Validating...</div>
)}
{formState.type === 'submitting' && (
<div>Submitting...</div>
)}
{formState.type === 'success' && (
<div>
<p>Success!</p>
<pre>{JSON.stringify(formState.data, null, 2)}</pre>
<button onclick={reset}>Start Over</button>
</div>
)}
{formState.type === 'error' && (
<div>
<p>Error: {formState.message}</p>
<button onclick={() => setFormState({ type: 'idle' })}>Retry</button>
</div>
)}
</div>
)
}State Machine with Guard Conditions
tsx
import { state } from 'flexium/core'
type GameState =
| { type: 'menu' }
| { type: 'playing'; score: number; level: number }
| { type: 'paused'; score: number; level: number }
| { type: 'gameOver'; score: number; level: number }
function Game() {
const [gameState, setGameState] = state<GameState>({ type: 'menu' })
const [score, setScore] = state(0)
const [level, setLevel] = state(1)
const startGame = () => {
// menu -> playing (guard: always allowed)
setGameState({ type: 'playing', score: 0, level: 1 })
setScore(0)
setLevel(1)
}
const pauseGame = () => {
// playing -> paused (guard: only when playing)
if (gameState.type === 'playing') {
setGameState({
type: 'paused',
score: gameState.score,
level: gameState.level
})
}
}
const resumeGame = () => {
// paused -> playing (guard: only when paused)
if (gameState.type === 'paused') {
setGameState({
type: 'playing',
score: gameState.score,
level: gameState.level
})
}
}
const endGame = () => {
// playing/paused -> gameOver (guard: only when playing or paused)
if (gameState.type === 'playing' || gameState.type === 'paused') {
setGameState({
type: 'gameOver',
score: gameState.score,
level: gameState.level
})
}
}
const increaseScore = (points: number) => {
// Only increase score when playing
if (gameState.type === 'playing') {
const newScore = gameState.score + points
setScore(newScore)
setGameState({
type: 'playing',
score: newScore,
level: gameState.level
})
}
}
return (
<div>
{gameState.type === 'menu' && (
<div>
<h1>Game Menu</h1>
<button onclick={startGame}>Start Game</button>
</div>
)}
{gameState.type === 'playing' && (
<div>
<h2>Playing</h2>
<p>Score: {gameState.score}</p>
<p>Level: {gameState.level}</p>
<button onclick={pauseGame}>Pause</button>
<button onclick={endGame}>End Game</button>
<button onclick={() => increaseScore(10)}>Score +10</button>
</div>
)}
{gameState.type === 'paused' && (
<div>
<h2>Paused</h2>
<p>Score: {gameState.score}</p>
<p>Level: {gameState.level}</p>
<button onclick={resumeGame}>Resume</button>
<button onclick={endGame}>End Game</button>
</div>
)}
{gameState.type === 'gameOver' && (
<div>
<h2>Game Over</h2>
<p>Final Score: {gameState.score}</p>
<p>Final Level: {gameState.level}</p>
<button onclick={startGame}>Play Again</button>
</div>
)}
</div>
)
}Action-Based State Machine
tsx
import { state } from 'flexium/core'
type Action =
| { type: 'LOAD' }
| { type: 'LOAD_SUCCESS'; data: any }
| { type: 'LOAD_ERROR'; error: Error }
| { type: 'RESET' }
type State =
| { type: 'idle' }
| { type: 'loading' }
| { type: 'success'; data: any }
| { type: 'error'; error: Error }
function ActionBasedStateMachine() {
const [state, setState] = state<State>({ type: 'idle' })
const dispatch = (action: Action) => {
switch (action.type) {
case 'LOAD':
setState({ type: 'loading' })
// Execute async action
fetch('/api/data')
.then(res => res.json())
.then(data => dispatch({ type: 'LOAD_SUCCESS', data }))
.catch(error => dispatch({ type: 'LOAD_ERROR', error }))
break
case 'LOAD_SUCCESS':
setState({ type: 'success', data: action.data })
break
case 'LOAD_ERROR':
setState({ type: 'error', error: action.error })
break
case 'RESET':
setState({ type: 'idle' })
break
}
}
return (
<div>
{state.type === 'idle' && (
<button onclick={() => dispatch({ type: 'LOAD' })}>Load</button>
)}
{state.type === 'loading' && (
<div>Loading...</div>
)}
{state.type === 'success' && (
<div>
<pre>{JSON.stringify(state.data, null, 2)}</pre>
<button onclick={() => dispatch({ type: 'RESET' })}>Reset</button>
</div>
)}
{state.type === 'error' && (
<div>
<p>Error: {state.error.message}</p>
<button onclick={() => dispatch({ type: 'LOAD' })}>Retry</button>
<button onclick={() => dispatch({ type: 'RESET' })}>Reset</button>
</div>
)}
</div>
)
}State Machine Utility Function
tsx
// utils/stateMachine.ts
export function createStateMachine<TState, TAction>(
initialState: TState,
reducer: (state: TState, action: TAction) => TState
) {
const [state, setState] = state(initialState)
const dispatch = (action: TAction) => {
setState(reducer(state, action))
}
return [state, dispatch] as const
}
// Usage example
type CounterAction =
| { type: 'INCREMENT' }
| { type: 'DECREMENT' }
| { type: 'RESET' }
type CounterState = { count: number }
function Counter() {
const [state, dispatch] = createStateMachine<CounterState, CounterAction>(
{ count: 0 },
(state, action) => {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 }
case 'DECREMENT':
return { count: state.count - 1 }
case 'RESET':
return { count: 0 }
default:
return state
}
}
)
return (
<div>
<p>Count: {state.count}</p>
<button onclick={() => dispatch({ type: 'INCREMENT' })}>+</button>
<button onclick={() => dispatch({ type: 'DECREMENT' })}>-</button>
<button onclick={() => dispatch({ type: 'RESET' })}>Reset</button>
</div>
)
}Related Documentation
- state() API - State API documentation
- Best Practices - Common Patterns - State machine patterns