State Management Patterns
Status: ✅ ActiveVersion: 4.0 (Controlled Components + Duplicate Prevention)
Last Updated: 2025-11-21
Added: Pattern 2 (Duplicate Prevention for Pickers)
Philosophy
Just like our Design System ensures UI consistency, our State Management Patterns ensure data consistency across all flows and components. Core Principle: Single Source of TruthPattern 1: Controlled Components
Industry Standard
This pattern is used by:- React Hook Form - Form state management
- Shopify Polaris - Enterprise component library
- Linear - Issue tracking app
- Notion - Document editing
- Formik - Form state library
How It Works
Implementation
FlowContainer (Orchestrator)
Responsibilities:- Restore saved responses from session
- Pass restored data as
initialValueto content types - Receive updates via
onValueChange - Persist to session storage
- Manage navigation
Content Type Components
Responsibilities:- Render UI
- Accept
initialValueprop - Manage internal UI state (e.g., slider position, text input)
- Call
onValueChange(value, isValid)when data changes - DO NOT directly access
sessionorAsyncStorage
Content Type Reference
Picker Steps (PickerType.tsx)
initialValue type:PickerSelection[]
Text Prompt Steps (TextPromptType.tsx)
initialValue type:string | Record<string, string>
Scripture Steps (ScriptureType.tsx)
initialValue type:{ selected: ScriptureVerse, userParaphrase?: string }
Breathing Steps (BreathingType.tsx)
initialValue type:boolean
Benefits
1. Predictable Data Flow
2. Consistent Navigation
Forward navigation:- Save response to session
- Reset
currentValueto null - Navigate to next step
- Navigate to previous step
- FlowContainer restores
currentValuefrom session - Component receives via
initialValue - Data appears ✅
3. Single Source of Truth
- ❌ OLD: 5 different components, 5 different restoration patterns
- ✅ NEW: 1 pattern, used consistently everywhere
4. Easy to Debug
5. Testable
Migration Checklist
When updating a content type component to this pattern:- Add
initialValueprop to component interface - Remove direct
sessionaccess - Remove
useFlow()hook (except for special cases) - Add
useEffectto initialize frominitialValue - Ensure
onValueChangeis called when data changes - Update FlowContainer to pass
initialValue - Test backward/forward navigation
Common Pitfalls
❌ DON’T: Access session directly in content types
✅ DO: Receive via initialValue
❌ DON’T: Call onValueChange in render
✅ DO: Call onValueChange in useEffect
❌ DON’T: Forget initialValue in dependencies
✅ DO: Include initialValue in dependencies
Testing
Unit Test (Content Type)
Integration Test (FlowContainer)
Pattern 2: Duplicate Prevention (Pickers)
Problem
When building multi-select pickers, users can accidentally select the same item multiple times:Solution
Always filter out selected options before rendering:Utilities (src/utils/pickerUtils.ts)
Generic option filtering functions:
Implementation Steps
Step 1: Import Utilities
Step 2: Compute Available Options
Step 3: Render Picker
Real-World Example: Emotion Selector
File:src/flow-engine/components/EmotionSelector.tsx
- ✅ Select “Joy” → “Joy” disappears from picker
- ✅ Select “Sadness” → Only 4 emotions remain
- ✅ Max 3 selections → Picker hidden
Best Practices
✅ DO:- Always use
useMemofor filtering (performance) - Extract IDs before filtering (clear intent)
- Use generic utilities (don’t rewrite logic)
- Don’t filter inside render (causes re-renders)
- Don’t duplicate filtering logic (use utilities)
- Don’t allow duplicates (always filter)
Testing Checklist
- Select option → Option disappears from picker
- Select max options → Picker hidden/disabled
- Remove option → Option reappears in picker
- Navigate back → State persists correctly
- No duplicates in selected list
- TypeScript: 0 errors
- Performance: useMemo prevents unnecessary re-renders
Related Documentation
- ARCHITECTURE.md - Overall system architecture
- DESIGN_SYSTEM.md - UI component patterns
- flow-engine/README.md - Flow engine overview
- pickerUtils.ts - Utility functions source code
Maintenance
When to update this doc:- Adding new state management pattern
- Adding new content type
- Discovering new edge cases
Maintained By: All contributors
Questions? Review implementations in:
src/flow-engine/content-types/(Controlled Components)src/flow-engine/components/EmotionSelector.tsx(Duplicate Prevention)src/utils/pickerUtils.ts(Utility functions)