state()
state() is the single, unified API for all state management in Flexium. It handles local state, shared global state, async data fetching, and derived values.
Important: Proxy Access Patterns
Flexium State is a Callable Proxy. While you can use it directly in arithmetic (count + 1) and JSX, Logic and Comparison require explicit handling.
We recommend the Function Call Syntax () for consistency:
const [count] = state(0);
const [isVisible] = state(true);
// ✅ Recommended: Function Call Syntax (Safe & Clear)
if (count() === 5) { ... }
if (!isVisible()) { ... }
// ⚠️ Arithmetic (Works directly)
const next = count + 1; // 1
// ❌ Avoid: Direct Proxy Comparison (Always fails)
if (count === 5) { ... } // false (Proxy !== number)
// ❌ Avoid: Direct Boolean Coercion (Always true)
if (!isVisible) { ... } // false (Proxy is always truthy)The state() API
The state function returns a tuple of [value, setter], similar to React's useState, but with supercharged capabilities. The value is a reactive proxy that can be used directly like a regular value.
import { state } from 'flexium/core';
const [count, setCount] = state(0);
// Use directly - no getter call needed!
console.log(count + 1); // 11. Local State
Local state is isolated to the component where it's created.
function Counter() {
const [count, setCount] = state(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, simply provide a unique key in the options.
// store/theme.ts
import { state } from 'flexium/core';
// Initialize with a default value
export const useTheme = () => state('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
import { useTheme } from './store/theme';
function Footer() {
// Accesses the SAME state because the key 'theme' matches
const [theme] = useTheme();
return <footer class={theme}>...</footer>;
}If multiple components call state() with the same key, they will share the same underlying signal.
3. Async State (Resources)
Pass an async function (or a function returning a Promise) to state() to create an async resource.
function UserProfile({ id }) {
// Automatically fetches when component mounts or dependencies change
const [user, refetch, status, error] = state(async () => {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) throw new Error('Failed to fetch');
return response.json();
});
// Render based on loading/error state
// status: 'idle' | 'loading' | 'success' | 'error'
if (status() === 'loading') return <div>Loading...</div>;
if (error()) return <div>Error: {error().message}</div>;
// Data is available - use values directly
return (
<div>
<h1>{user.name}</h1>
<button onclick={() => refetch()}>Reload</button>
</div>
);
}- Automatic Tracking: If the async function uses other signals, it will auto-refetch when they change.
- Return Values:
[data, refetch, status, error]- all reactive proxies. Status is'idle' | 'loading' | 'success' | 'error'.
4. Computed State (Derived)
Pass a synchronous function to derive state from other signals.
const [count, setCount] = state(1);
// 'double' updates whenever 'count' changes
const [double] = state(() => count * 2);
console.log(double); // 2
setCount(5);
console.log(double); // 10Computed state is read-only by default (the setter is no-op or throws, depending on config).
Effects & Side Effects
While state() manages data, effect() handles side effects like DOM manipulation, logging, or subscriptions.
import { state, effect } from 'flexium/core';
const [count, setCount] = state(0);
effect(() => {
// Automatically runs when 'count' changes
console.log('Count is:', count);
});For detailed usage, automatic tracking, and cleanup, see the Effects guide.
List Rendering
For rendering lists efficiently, use familiar .map() syntax - just like React:
const [todos, setTodos] = state([{ id: 1, text: 'Buy milk' }]);
return (
<ul>
{todos.map((todo, index) => (
<li key={todo.id}>{index + 1}: {todo.text}</li>
))}
</ul>
);Flexium automatically optimizes list rendering with O(1) append/prepend and DOM node caching.
5. Array Keys
Keys can be arrays for hierarchical namespacing - similar to TanStack Query:
// String key
const [user] = state(null, { key: 'user' })
// Array key - great for dynamic keys
const [user] = state(null, { key: ['user', 'profile', userId] })
const [posts] = state([], { key: ['user', 'posts', userId] })6. Params Option
Pass explicit parameters to functions for better DX:
// Implicit dependencies (closure)
const [user] = state(async () => fetch(`/api/users/${userId}`))
// Explicit dependencies (params) - recommended for complex cases
const [user] = state(
async ({ userId, postId }) => fetch(`/api/users/${userId}/posts/${postId}`),
{
key: ['user', 'posts', userId, postId],
params: { userId, postId }
}
)Benefits:
- Self-documenting code
- Better DevTools visibility
- Improved TypeScript inference
Best Practices
- Use
state()for everything: It's the universal primitive. - Destructure the tuple:
const [val, setVal] = state(...)is the standard pattern. - Use
()for Logic/Comparison:if (count() === 10)is safer than implicit coercion. - Use values directly:
count + 1works automatically thanks to Symbol.toPrimitive. - Use array keys for dynamic data:
['user', userId]instead of'user-' + userId. - Use params for explicit dependencies: Makes code self-documenting.