Files
metabuilder/hooks/MUTATION_HOOKS.md
johndoe6345789 5aabff44cd refactor(fakemui): flatten QML components directory structure and update documentation
Directory Restructuring:
- qml/qml-components/qml-components/* → qml/components/ (flattens nesting)
- All 104 QML files moved with git history preserved
- Eliminates redundant qml-components nesting

Documentation Updates:
- ARCHITECTURE.md: Updated qml/components references (2 locations)
- GETTING_STARTED.md: Updated qml/components path (1 location, end of file)
- README.md: Updated qml/components references (3 locations)
- CODE_REVIEW.md: Updated qml/components file paths (4 locations)
- docs/ARCHITECTURE.md: Complete refactor with qml/components paths

Verification:
-  No remaining qml-components/ references in documentation
-  All 104 QML files present in flattened structure
-  Directory structure verified (12 component categories)
-  First-class directory naming convention

Structure Post-Refactor:
qml/
├── components/
│   ├── atoms/ (16 files)
│   ├── core/ (11 files)
│   ├── data-display/ (10 files)
│   ├── feedback/ (11 files)
│   ├── form/ (19 files)
│   ├── lab/ (11 files)
│   ├── layout/ (12 files)
│   ├── navigation/ (12 files)
│   ├── surfaces/ (7 files)
│   ├── theming/ (4 files)
│   └── utils/ (13 files)
├── hybrid/
└── widgets/

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-01-23 19:54:21 +00:00

8.3 KiB

State Mutation Hooks

This document describes the 5 new state mutation hooks added to /hooks/. These hooks provide reusable state management patterns for common React use cases.

Overview

Hook Purpose Return Type
useToggle Boolean state toggle { value, toggle, setValue, setTrue, setFalse }
usePrevious Track previous value T | undefined
useStateWithHistory State with undo/redo { value, setValue, undo, redo, canUndo, canRedo, history }
useAsync Async function wrapper { data, loading, error, execute, reset }
useUndo Simplified undo/redo { value, setValue, undo, redo, reset, canUndo, canRedo }

Detailed API Reference

1. useToggle

Boolean state management with multiple toggle options.

Import:

import { useToggle } from '@/hooks/useToggle'

Signature:

function useToggle(initialValue?: boolean): UseToggleReturn

interface UseToggleReturn {
  value: boolean
  toggle: () => void
  setValue: (value: boolean | ((prev: boolean) => boolean)) => void
  setTrue: () => void
  setFalse: () => void
}

Example:

const { value, toggle, setValue, setTrue, setFalse } = useToggle(false)

return (
  <div>
    <p>Menu is {value ? 'open' : 'closed'}</p>
    <Button onClick={toggle}>Toggle Menu</Button>
    <Button onClick={setTrue}>Open</Button>
    <Button onClick={setFalse}>Close</Button>
  </div>
)

Use Cases:

  • Modal/dialog visibility
  • Show/hide menu or panel
  • Conditional rendering
  • Toggle features on/off

2. usePrevious

Track the previous value of any state or prop across renders.

Import:

import { usePrevious } from '@/hooks/usePrevious'

Signature:

function usePrevious<T>(value: T): T | undefined

Example:

const [count, setCount] = useState(0)
const prevCount = usePrevious(count)

return (
  <div>
    <p>Current: {count}</p>
    <p>Previous: {prevCount ?? 'none'}</p>
    <Button onClick={() => setCount(count + 1)}>Increment</Button>
  </div>
)

Use Cases:

  • Detect value changes
  • Compare current vs previous
  • Form dirty state tracking
  • Animation triggers based on value changes
  • Conditional effects

Important: Returns undefined on first render.


3. useStateWithHistory

State management with full undo/redo history capability.

Import:

import { useStateWithHistory } from '@/hooks/useStateWithHistory'

Signature:

function useStateWithHistory<T>(
  initialValue: T,
  options?: UseStateWithHistoryOptions
): UseStateWithHistoryReturn<T>

interface UseStateWithHistoryOptions {
  maxHistory?: number // default: 100
}

interface UseStateWithHistoryReturn<T> {
  value: T
  setValue: (value: T | ((prev: T) => T)) => void
  undo: () => void
  redo: () => void
  canUndo: boolean
  canRedo: boolean
  history: T[]
}

Example:

const { value, setValue, undo, redo, canUndo, canRedo, history } = useStateWithHistory('initial')

return (
  <div>
    <p>Current: {value}</p>
    <p>History size: {history.length}</p>
    <TextField value={value} onChange={(e) => setValue(e.target.value)} />
    <Button onClick={undo} disabled={!canUndo}>Undo</Button>
    <Button onClick={redo} disabled={!canRedo}>Redo</Button>
  </div>
)

Use Cases:

  • Text editor undo/redo
  • Form draft history
  • Canvas drawing operations
  • Configuration changes tracking
  • Multi-step workflows

Features:

  • Limits history size (configurable)
  • Removes "future" history when new value set after undo
  • Full history array accessible
  • Type-safe for any value type

4. useAsync

Async function wrapper with automatic loading, error, and data state management.

Import:

import { useAsync } from '@/hooks/useAsync'

Signature:

function useAsync<T>(
  asyncFunction: (...args: any[]) => Promise<T>,
  deps?: any[],
  options?: UseAsyncOptions
): UseAsyncReturn<T>

interface UseAsyncOptions {
  immediate?: boolean // default: false - execute on mount
  resetErrorOnRetry?: boolean // default: true
  resetDataOnRetry?: boolean // default: false
}

interface UseAsyncReturn<T> {
  data: T | undefined
  loading: boolean
  error: Error | undefined
  execute: (...args: any[]) => Promise<T | undefined>
  reset: () => void
}

Example - Manual Execution:

const fetchUser = async (userId: string) => {
  const res = await fetch(`/api/users/${userId}`)
  return res.json()
}

const { data, loading, error, execute } = useAsync(fetchUser)

return (
  <div>
    {loading && <Spinner />}
    {error && <Alert severity="error">{error.message}</Alert>}
    {data && <UserProfile user={data} />}
    <Button onClick={() => execute('123')}>Fetch User</Button>
  </div>
)

Example - Automatic Execution:

const { data: posts, loading, error } = useAsync(
  async () => {
    const res = await fetch('/api/posts')
    return res.json()
  },
  [],
  { immediate: true }
)

return (
  <div>
    {loading && <Skeleton />}
    {posts?.map(post => <PostCard key={post.id} post={post} />)}
  </div>
)

Use Cases:

  • API data fetching
  • Async operations with loading states
  • Error handling and user feedback
  • Retry mechanisms
  • Form submissions

Features:

  • Prevents state updates after unmount
  • Configurable error/data reset on retry
  • Pass arguments to async function
  • Reset all states

5. useUndo

Simplified undo/redo wrapper for any value, lighter than useStateWithHistory.

Import:

import { useUndo } from '@/hooks/useUndo'

Signature:

function useUndo<T>(initialValue: T): UseUndoReturn<T>

interface UseUndoReturn<T> {
  value: T
  setValue: (value: T | ((prev: T) => T)) => void
  undo: () => void
  redo: () => void
  reset: (value?: T) => void
  canUndo: boolean
  canRedo: boolean
}

Example:

const { value, setValue, undo, redo, reset, canUndo, canRedo } = useUndo({ x: 0, y: 0 })

return (
  <div>
    <Canvas
      position={value}
      onChange={(newPos) => setValue(newPos)}
    />
    <Toolbar>
      <Button onClick={undo} disabled={!canUndo}>Undo</Button>
      <Button onClick={redo} disabled={!canRedo}>Redo</Button>
      <Button onClick={() => reset()}>Reset</Button>
    </Toolbar>
  </div>
)

Use Cases:

  • Simple undo/redo without full history
  • Lightweight state tracking
  • Form value changes
  • Canvas position/zoom
  • Configuration adjustments

Differences from useStateWithHistory:

  • No maxHistory limit (stores only past, present, future)
  • Simpler implementation, smaller bundle
  • Good for finite undo/redo scenarios
  • No history array access

Comparison Table

Feature useToggle usePrevious useStateWithHistory useAsync useUndo
Tracks state ✗ (read-only) ✓ (data)
Undo capability
History array
Async support
Bundle size Small Tiny Medium Medium Small
Generic typing ✗ (boolean)

Installation & Usage

All hooks are located in /hooks/ and can be imported directly:

import { useToggle } from '@/hooks/useToggle'
import { usePrevious } from '@/hooks/usePrevious'
import { useStateWithHistory } from '@/hooks/useStateWithHistory'
import { useAsync } from '@/hooks/useAsync'
import { useUndo } from '@/hooks/useUndo'

Or using absolute imports:

import { useToggle } from '/path/to/hooks/useToggle'

Best Practices

  1. useToggle - Use for simple boolean states. Don't overcomplicate with extra logic.
  2. usePrevious - Always check for undefined on first render.
  3. useStateWithHistory - Set reasonable maxHistory limits to prevent memory issues.
  4. useAsync - Always handle the error state for better UX.
  5. useUndo - Use for lightweight undo/redo, not complex workflows.

Performance Considerations

  • All hooks use useCallback to memoize functions where appropriate
  • useAsync prevents state updates after unmount
  • useStateWithHistory includes configurable history size limits
  • usePrevious uses useRef (no re-renders)
  • useToggle and useUndo are optimized for minimal re-renders