Skip to content

use()

use() is the single, unified API for all state management in Flexium. It handles local state, shared global state, async data fetching, and derived values.

The use() API

The use() function always returns a tuple:

tsx
import { use } from 'flexium/core'

// Returns [value, setter]
const [count, setCount] = use(0)

// Read the value
console.log(count + 1)  // 1

// Update the value
setCount(5)
setCount(prev => prev + 1)

1. Local State

Local state is isolated to the component where it's created.

tsx
function Counter() {
  const [count, setCount] = use(0)

  return (
    <div>
      <p>Count: {count}</p>
      <button onclick={() => setCount(c => c + 1)}>Increment</button>
    </div>
  )
}
  • Reading: Use the value directly in expressions count + 1 or in JSX {count}
  • Writing: Call the setter setCount(newValue) or setCount(prev => prev + 1)

2. Global State

To share state across components, provide a unique key in the options.

tsx
// store/theme.ts
import { use } from 'flexium/core'

export const useTheme = () => use('light', { key: ['theme'] })
tsx
// Header.tsx
import { useTheme } from './store/theme'

function Header() {
  const [theme, setTheme] = useTheme()

  return (
    <header class={theme}>
      <h1>My App</h1>
      <button onclick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
        Toggle Theme
      </button>
    </header>
  )
}

// Footer.tsx - shares the same state
function Footer() {
  const [theme] = useTheme()
  return <footer class={theme}>...</footer>
}

3. Async State (Resources)

Pass an async function to use() to create an async resource.

tsx
function UserProfile({ id }) {
  const [user, control] = use(async () => {
    const response = await fetch(`/api/users/${id}`)
    if (!response.ok) throw new Error('Failed to fetch')
    return response.json()
  })

  if (control.loading) return <div>Loading...</div>
  if (control.error) return <div>Error: {control.error.message}</div>

  return (
    <div>
      <h1>{user.name}</h1>
      <button onclick={() => control.refetch()}>Reload</button>
    </div>
  )
}

ResourceControl properties:

  • loading: boolean - true while fetching
  • error: unknown - error object if failed
  • status: 'idle' | 'loading' | 'success' | 'error'
  • refetch(): Promise<void> - re-trigger the async operation

4. Computed State (Derived)

Pass a synchronous function with deps to derive state from other values.

tsx
const [count, setCount] = use(1)

// Updates when count changes
const [double] = use(() => count * 2, [count])

console.log(double) // 2
setCount(5)
console.log(double) // 10

5. deps Option

For expensive computations that should only re-run when specific dependencies change, use the deps option:

tsx
const [items] = useItems()
const [filter, setFilter] = use('all')

// Only recomputes when items or filter changes
const [filteredItems] = use(() => {
  return items.filter(item =>
    filter === 'all' ? true : item.status === filter
  )
}, [items, filter])

When to use deps:

  • Expensive calculations (sorting, filtering large lists)
  • When you need explicit control over recomputation
  • Migrating from React's useMemo

Computed state requires deps:

UsageExample
With depsuse(() => count * 2, [count])
Re-runs when deps changeExplicit dependency tracking
tsx
// Example: Kanban board with memoized columns
function KanbanBoard() {
  const [tasks] = useTasks()

  const [todo] = use(() => tasks.filter(t => t.status === 'todo'), [tasks])
  const [inProgress] = use(() => tasks.filter(t => t.status === 'in-progress'), [tasks])
  const [done] = use(() => tasks.filter(t => t.status === 'done'), [tasks])

  return (
    <div class="kanban">
      <Column tasks={todo} title="To Do" />
      <Column tasks={inProgress} title="In Progress" />
      <Column tasks={done} title="Done" />
    </div>
  )
}

WARNING

The deps option is not supported with async functions. Use automatic tracking or the key option for async state.

Effects & Side Effects

While use() manages data, use() also handles side effects like DOM manipulation, logging, or subscriptions.

tsx
import { use } from 'flexium/core'

const [count, setCount] = use(0)

use(({ onCleanup }) => {
  // Runs when 'count' changes
  console.log('Count is:', count)
}, [count])

For detailed usage, automatic tracking, and cleanup, see the Effects guide.

List Rendering

For rendering lists efficiently, use familiar .map() syntax:

tsx
const [todos, setTodos] = use([{ id: 1, text: 'Buy milk' }])

return (
  <ul>
    {todos.map((todo, index) => (
      <li key={todo.id}>{index + 1}: {todo.text}</li>
    ))}
  </ul>
)

6. Array Keys

Keys can be arrays for hierarchical namespacing - similar to TanStack Query:

tsx
// String key (deprecated - use array)
const [user, setUser] = use(null, { key: ['user'] })

// Array key - great for dynamic keys (recommended)
const [userProfile] = use(null, { key: ['user', 'profile', userId] })
const [posts] = use([], { key: ['user', 'posts', userId] })

7. Params Option

Pass explicit parameters to functions for better DX:

tsx
// Implicit dependencies (closure)
const [user] = use(async () => fetch(`/api/users/${userId}`))

// Explicit dependencies (params) - recommended for complex cases
const [userWithParams, control] = use(
  async ({ userId, postId }) => fetch(`/api/users/${userId}/posts/${postId}`),
  undefined,
  {
    key: ['user', 'posts', userId, postId],
    params: { userId, postId }
  }
)

Benefits:

  • Self-documenting code
  • Better DevTools visibility
  • Improved TypeScript inference

Best Practices

  1. Destructure the tuple: const [value, setter] = use(initial)
  2. Use setter for updates: setter(newValue) or setter(prev => prev + 1)
  3. Use deps for expensive computations: use(() => ..., [deps])
  4. Use array keys for dynamic data: ['user', userId] instead of 'user-' + userId

Released under the MIT License.