State Management

Learn how to manage state effectively with Storken.

Basic State Operations

Setting State

Set state directly with values or use callback functions:

const [count, setCount] = useStorken('count', 0)

// Direct value
setCount(10)

// Callback function (like useState)
setCount(prev => prev + 1)

// Async updates
const handleUpdate = async () => {
  const newValue = await fetchValue()
  setCount(newValue)
}

Resetting State

Reset state to its initial value:

const [count, setCount, resetCount] = useStorken('count', 0)

// Reset to initial value (0)
resetCount()

// Reset multiple states
const [user, , resetUser] = useStorken('user')
const [theme, , resetTheme] = useStorken('theme')

const resetAll = () => {
  resetUser()
  resetTheme()
  resetCount()
}

Global State Management

State is automatically shared across all components using the same key:

// Header.tsx
function Header() {
  const [user] = useStorken<User>('user')
  return <div>Welcome, {user?.name}</div>
}

// Profile.tsx
function Profile() {
  const [user, setUser] = useStorken<User>('user')
  
  const updateName = (name: string) => {
    setUser({ ...user, name })
  }
  
  return (
    <input 
      value={user?.name} 
      onChange={e => updateName(e.target.value)}
    />
  )
}

// Both components share the same 'user' state

Complex State Updates

Array State

const [todos, setTodos] = useStorken<Todo[]>('todos', [])

// Add item
const addTodo = (todo: Todo) => {
  setTodos(prev => [...prev, todo])
}

// Remove item
const removeTodo = (id: string) => {
  setTodos(prev => prev.filter(t => t.id !== id))
}

// Update item
const updateTodo = (id: string, updates: Partial<Todo>) => {
  setTodos(prev => prev.map(t => 
    t.id === id ? { ...t, ...updates } : t
  ))
}

// Sort items
const sortByDate = () => {
  setTodos(prev => [...prev].sort((a, b) => 
    a.createdAt.getTime() - b.createdAt.getTime()
  ))
}

Object State

interface Settings {
  theme: 'light' | 'dark'
  notifications: boolean
  language: string
  privacy: {
    shareData: boolean
    showProfile: boolean
  }
}

const [settings, setSettings] = useStorken<Settings>('settings', {
  theme: 'light',
  notifications: true,
  language: 'en',
  privacy: {
    shareData: false,
    showProfile: true
  }
})

// Update single property
setSettings(prev => ({ ...prev, theme: 'dark' }))

// Update nested property
setSettings(prev => ({
  ...prev,
  privacy: {
    ...prev.privacy,
    shareData: true
  }
}))

// Batch updates
const updateSettings = (updates: Partial<Settings>) => {
  setSettings(prev => ({ ...prev, ...updates }))
}

State with Side Effects

Use setters to trigger side effects when state changes:

const [useStorken] = create({
  setters: {
    // Save to backend when cart changes
    cart: async (storken, items: CartItem[]) => {
      await fetch('/api/cart', {
        method: 'PUT',
        body: JSON.stringify(items)
      })
      
      // Update related states
      const total = items.reduce((sum, item) => 
        sum + item.price * item.quantity, 0
      )
      storken.sky.set('cartTotal', total)
    },
    
    // Log state changes
    user: async (storken, user: User) => {
      console.log('User updated:', user)
      
      // Track analytics
      analytics.track('user_update', {
        userId: user.id,
        timestamp: Date.now()
      })
    }
  }
})

// Usage - side effects run automatically
const [cart, setCart, , loading] = useStorken('cart')
// loading becomes true during side effect execution

Optimistic Updates

Update UI immediately while syncing with backend:

const [useStorken] = create({
  setters: {
    todos: async (storken, todos: Todo[], optimistic = true) => {
      // Optimistically update UI first
      if (optimistic) {
        storken.set(todos)
      }
      
      try {
        // Sync with backend
        const response = await fetch('/api/todos', {
          method: 'PUT',
          body: JSON.stringify(todos)
        })
        
        if (!response.ok) {
          throw new Error('Failed to save')
        }
        
        // Update with server response if different
        const serverTodos = await response.json()
        if (!optimistic) {
          storken.set(serverTodos)
        }
      } catch (error) {
        // Revert on error
        console.error('Failed to save todos:', error)
        storken.reset() // Revert to previous state
        throw error
      }
    }
  }
})

State Persistence

Persist state across sessions using plugins:

// Create persistence plugin
const persistPlugin = (storken) => {
  const key = `app_${storken.key}`
  
  // Load from localStorage on init
  const saved = localStorage.getItem(key)
  if (saved) {
    try {
      storken.set(JSON.parse(saved))
    } catch (e) {
      console.error('Failed to load persisted state:', e)
    }
  }
  
  // Save to localStorage on change
  storken.on('set', (value) => {
    try {
      localStorage.setItem(key, JSON.stringify(value))
    } catch (e) {
      console.error('Failed to persist state:', e)
    }
  })
  
  return {
    clear: () => localStorage.removeItem(key),
    isPersisted: () => localStorage.getItem(key) !== null
  }
}

// Use with selected states
const [useStorken] = create({
  initialValues: {
    theme: 'light',
    user: null,
    tempData: {} // Not persisted
  },
  plugins: {
    persist: persistPlugin
  }
})

// Access persistence methods
const [theme, , , , , plugins] = useStorken('theme')
plugins.persist.clear() // Clear persisted data

✅ Best Practices

  • • Keep state minimal - derive what you can
  • • Use callbacks for updates based on previous state
  • • Handle errors in async operations
  • • Use TypeScript for type safety
  • • Consider using setters for side effects
  • • Implement optimistic updates for better UX