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:
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.
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 + 1or in JSX{count} - Writing: Call the setter
setCount(newValue)orsetCount(prev => prev + 1)
2. Global State
To share state across components, provide a unique key in the options.
// store/theme.ts
import { use } from 'flexium/core'
export const useTheme = () => use('light', { key: ['theme'] })// 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.
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 fetchingerror: unknown- error object if failedstatus: '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.
const [count, setCount] = use(1)
// Updates when count changes
const [double] = use(() => count * 2, [count])
console.log(double) // 2
setCount(5)
console.log(double) // 105. deps Option
For expensive computations that should only re-run when specific dependencies change, use the deps option:
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:
| Usage | Example |
|---|---|
| With deps | use(() => count * 2, [count]) |
| Re-runs when deps change | Explicit dependency tracking |
// 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.
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:
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:
// 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:
// 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
- Destructure the tuple:
const [value, setter] = use(initial) - Use setter for updates:
setter(newValue)orsetter(prev => prev + 1) - Use deps for expensive computations:
use(() => ..., [deps]) - Use array keys for dynamic data:
['user', userId]instead of'user-' + userId