-
Notifications
You must be signed in to change notification settings - Fork 8
Juris FluentState Plugin Developer's Guide
A powerful, intuitive reactive state management system for Juris applications. FluentState provides a fluent proxy-based API that makes working with state feel natural and effortless.
- Quick Setup
- Core Concepts
- Usage Patterns
- Reactivity
- Advanced Features
- Best Practices
- API Reference
- Complete Examples
FluentState integrates seamlessly with Juris through the headless component system. Always define your base global state structure in the Juris config:
const juris = new Juris({ // ✅ Define base state structure here states: { user: { name: 'Guest', email: '', profile: { age: null, preferences: { theme: 'light', notifications: true } } }, todos: { items: [], filter: 'all', newTodo: '' }, ui: { loading: false, modals: { settings: { visible: false }, help: { visible: false } }, sidebar: { collapsed: false } }, config: { version: '1.0.0', debugMode: false, api: { baseUrl: 'https://api.example.com', timeout: 5000 } } }, features: { headless: HeadlessManager }, headlessComponents: { fluentState: { fn: createFluentStateHeadless, options: { autoInit: true } } }, components: { /* your components */ }, layout: { /* your layout */ } });
1. Single Source of Truth
// ✅ Good - state structure is clear and centralized const juris = new Juris({ states: { app: { initialized: false, version: '1.0.0' }, user: { authenticated: false, profile: { name: '', email: '' } } }, // ... rest of config }); // Components just use the pre-defined structure const MyComponent = (props, context) => { const { app, user } = context.fluentState.getFluentStates(); // State is already properly structured return { div: { text: () => `${user.profile.name} - v${app.version}` } }; };
2. Avoid Scattered Initialization
// ❌ Bad - state initialization scattered across components const ComponentA = (props, context) => { const { user } = context.fluentState.getFluentStates(); user.profile = user.profile || {}; // Initializing here user.profile.preferences = user.profile.preferences || {}; // And here // ... }; const ComponentB = (props, context) => { const { user } = context.fluentState.getFluentStates(); user.settings = user.settings || {}; // Different component initializing different parts // ... }; // ✅ Good - all initialization in config const juris = new Juris({ states: { user: { profile: { preferences: { theme: 'light', lang: 'en' } }, settings: { notifications: true } } } // ... });
With base state defined in config, components become much cleaner:
const UserProfile = (props, context) => { const { user } = context.fluentState.getFluentStates(); // No initialization needed - state structure already exists return { div: { class: 'user-profile', children: [ { input: { type: 'text', value: () => user.profile.name, oninput: (e) => user.profile.name = e.target.value }}, { select: { value: () => user.profile.preferences.theme, onchange: (e) => user.profile.preferences.theme = e.target.value, children: [ { option: { value: 'light', text: 'Light' } }, { option: { value: 'dark', text: 'Dark' } } ] }} ] } }; };
Understanding the fundamental difference between FluentState and props is crucial for effective usage:
FluentState = Objects (References)
const MyComponent = (props, context) => { const { user, config } = context.fluentState.getFluentStates(); // user and config are OBJECT REFERENCES // When you modify them, you're modifying the actual state object user.name = 'John'; // ✅ Modifies the state object user.profile.age = 25; // ✅ Modifies nested object config.theme = 'dark'; // ✅ Modifies the state object // All components sharing this state see the changes immediately return { div: { text: () => user.name } // Shows 'John' }; };
Props = Primitives (Values)
const ChildComponent = (props, context) => { // props contains PRIMITIVE VALUES (copies) console.log(props.userName); // 'John' (a copy of the value) console.log(props.userAge); // 25 (a copy of the value) // You CANNOT modify parent state through props props.userName = 'Jane'; // ❌ Only changes local copy, not parent state // To modify parent state, use FluentState const { user } = context.fluentState.getFluentStates(); user.name = 'Jane'; // ✅ Modifies actual state return { div: { text: () => `Props: ${props.userName}, State: ${user.name}` } }; };
1. State Mutations Work
const TodoApp = (props, context) => { const { todos } = context.fluentState.getFluentStates(); todos.items = todos.items || []; // ✅ This works - you're modifying the state object const addTodo = (text) => { todos.items.push({ id: Date.now(), text, done: false }); }; // ✅ This works - you're modifying array elements const toggleTodo = (index) => { todos.items[index].done = !todos.items[index].done; }; return { div: { children: [ () => todos.items.map((todo, index) => ({ div: { key: todo.id, children: [ { span: { text: todo.text } }, { button: { text: 'Toggle', onclick: () => toggleTodo(index) }} ] } })) ] } }; };
2. Props Are Read-Only Values
const UserCard = (props, context) => { // props.user is a COPY of primitive values, not the object reference const userName = props.user?.name || 'Unknown'; const userAge = props.user?.age || 0; // ❌ This doesn't work - props are copies const updateNameWrong = () => { props.user.name = 'New Name'; // Only changes local copy }; // ✅ This works - access state directly const updateNameRight = () => { const { user } = context.fluentState.getFluentStates(); user.name = 'New Name'; // Modifies actual state }; return { div: { children: [ { p: { text: `Name: ${userName}` } }, { button: { text: 'Update (Wrong)', onclick: updateNameWrong } }, { button: { text: 'Update (Right)', onclick: updateNameRight } } ] } }; };
3. Sharing State Between Components
// Parent Component const App = (props, context) => { const { shared } = context.fluentState.getFluentStates(); // Initialize shared state shared.counter = shared.counter || 0; shared.user = shared.user || { name: 'Guest' }; return { div: { children: [ // Both components access the SAME state objects { ComponentA: {} }, { ComponentB: {} } ] } }; }; // Component A const ComponentA = (props, context) => { const { shared } = context.fluentState.getFluentStates(); return { div: { children: [ { p: { text: () => `A sees: ${shared.counter}` } }, { button: { text: 'A increment', onclick: () => shared.counter++ // Modifies shared object }} ] } }; }; // Component B const ComponentB = (props, context) => { const { shared } = context.fluentState.getFluentStates(); return { div: { children: [ // Sees the SAME counter that Component A modifies { p: { text: () => `B sees: ${shared.counter}` } }, { button: { text: 'B increment', onclick: () => shared.counter++ // Modifies same shared object }} ] } }; };
Think of FluentState as shared objects in memory:
// This is conceptually what FluentState does: const globalStateObjects = { user: { name: 'John', age: 25 }, todos: { items: [], filter: 'all' }, ui: { theme: 'dark', loading: false } }; // Every component gets references to the same objects: const ComponentA = () => { const user = globalStateObjects.user; // Reference to same object const todos = globalStateObjects.todos; // Reference to same object user.name = 'Jane'; // All components see this change todos.items.push(...); // All components see this change }; const ComponentB = () => { const user = globalStateObjects.user; // Same object reference console.log(user.name); // 'Jane' - sees ComponentA's change };
❌ Treating FluentState Like Props
// Wrong - thinking you need to "pass down" state const Parent = (props, context) => { const { user } = context.fluentState.getFluentStates(); return { // ❌ Don't do this - Child can access user directly Child: { userName: user.name, userAge: user.age } }; }; // ✅ Correct - access state directly where needed const Child = (props, context) => { const { user } = context.fluentState.getFluentStates(); return { div: { text: () => user.name } // Direct access to state }; };
❌ Trying to Modify Props
const Component = (props, context) => { // ❌ This won't work - props are primitive copies const updateUser = () => { props.user.name = 'New Name'; }; // ✅ Use FluentState for modifications const updateUserCorrect = () => { const { user } = context.fluentState.getFluentStates(); user.name = 'New Name'; }; };
Pattern 1: Root Proxy ($)
const { $ } = context.fluentState.getFluentStates(); $.counter = 0; $.user.name = 'John'; $.app.settings.theme = 'dark';
Pattern 2: Destructured Paths
const { user, counter, settings } = context.fluentState.getFluentStates(); counter.value = 0; user.name = 'John'; settings.theme = 'dark';
Pattern 3: Mixed Approach
const { $, user, counter } = context.fluentState.getFluentStates(); // Use destructured paths for frequently accessed state user.profile = { name: 'John', email: 'john@example.com' }; counter.clicks = 0; // Use $ for occasional or global state $.lastActivity = Date.now(); $.debugMode = false;
FluentState offers flexible ways to access and organize your state:
FluentState automatically creates nested object structures as you access them:
const { data } = context.fluentState.getFluentStates(); // All intermediate paths are created automatically data.user.profile.preferences.notifications.email = true; // Results in: // { // user: { // profile: { // preferences: { // notifications: { // email: true // } // } // } // } // }
const ProfileComponent = (props, context) => { const { user } = context.fluentState.getFluentStates(); // Initialize state (safe to do repeatedly) user.name = user.name || 'Anonymous'; user.email = user.email || ''; return { div: { class: 'profile', children: [ { input: { type: 'text', value: () => user.name, oninput: (e) => user.name = e.target.value }}, { input: { type: 'email', value: () => user.email, oninput: (e) => user.email = e.target.value }}, { p: { text: () => `Welcome, ${user.name}!` } } ] } }; };
const TodoList = (props, context) => { const { todos } = context.fluentState.getFluentStates(); // Initialize array todos.items = todos.items || []; const addTodo = (text) => { todos.items.push({ id: Date.now(), text: text, completed: false }); }; return { div: { children: [ { button: { text: 'Add Todo', onclick: () => addTodo('New task') }}, () => todos.items.map(todo => ({ div: { key: todo.id, text: todo.text, onclick: () => todo.completed = !todo.completed } })) ] } }; };
✅ Define Base State in Juris Config
const juris = new Juris({ states: { // Application state app: { initialized: true, version: '1.0.0', startTime: Date.now() }, // User state with complete structure user: { authenticated: false, profile: { name: 'Guest', email: '', avatar: null }, preferences: { theme: 'light', language: 'en', notifications: { email: true, push: false } } }, // Feature-specific state todos: { items: [], filter: 'all', stats: { total: 0, completed: 0, remaining: 0 } }, // UI state ui: { loading: false, errors: [], modals: { settings: { visible: false }, confirm: { visible: false, message: '' } } } }, // ... rest of config }); // Components are now clean and predictable const TodoApp = (props, context) => { const { todos, ui } = context.fluentState.getFluentStates(); // No initialization needed - structure already exists const addTodo = (text) => { todos.items.push({ id: Date.now(), text: text.trim(), completed: false }); // Update stats todos.stats.total++; todos.stats.remaining++; }; return { div: { class: 'todo-app', children: [ // All paths are guaranteed to exist { p: { text: () => `${todos.stats.remaining} remaining` } }, { div: { class: () => ui.loading ? 'loading' : '', children: [ () => todos.items.map(todo => ({ /* todo item */ })) ] }} ] } }; };
❌ Avoid Component-Level Initialization
// Don't do this - scattered initialization const BadComponent = (props, context) => { const { todos, user } = context.fluentState.getFluentStates(); // ❌ Initialization mixed with component logic todos.items = todos.items || []; todos.filter = todos.filter || 'all'; user.profile = user.profile || {}; user.profile.preferences = user.profile.preferences || {}; // Component logic becomes unclear return { /* component */ }; }; // ✅ Good - clean component that just uses state const GoodComponent = (props, context) => { const { todos, user } = context.fluentState.getFluentStates(); // State structure is guaranteed - just use it return { div: { text: () => `${user.profile.name}: ${todos.items.length} todos` } }; };
For larger applications, organize state by feature domains:
const juris = new Juris({ states: { // Authentication domain auth: { user: null, token: null, refreshToken: null, loginAttempts: 0, session: { expiresAt: null, lastActivity: null } }, // Data domains products: { items: [], categories: [], filters: { category: 'all', priceRange: { min: 0, max: 1000 }, sortBy: 'name' }, pagination: { page: 1, limit: 20, total: 0 } }, // Shopping cart domain cart: { items: [], totals: { subtotal: 0, tax: 0, shipping: 0, total: 0 }, checkout: { step: 1, shippingAddress: null, paymentMethod: null } }, // UI/UX domain ui: { theme: 'light', layout: 'grid', modals: { productDetail: { visible: false, productId: null }, cart: { visible: false }, checkout: { visible: false } }, notifications: { items: [], unreadCount: 0 } }, // Application meta app: { version: '2.1.0', environment: 'production', features: { darkMode: true, notifications: true, analytics: false }, api: { baseUrl: 'https://api.myapp.com', timeout: 30000, retries: 3 } } }, // ... rest of config });
Unlike traditional frameworks, Juris doesn't re-render components. Instead, it uses automatic dependency tracking and fine-grained reactivity:
-
Reactive functions (like
() => user.name) automatically update when their dependencies change - No component re-rendering - only specific reactive expressions update
- Automatic tracking - Juris tracks which state paths each reactive function depends on
- Precise updates - only the exact DOM elements that need to change are updated
Any function in your component template becomes reactive and updates automatically:
const Counter = (props, context) => { const { counter } = context.fluentState.getFluentStates(); counter.value = counter.value || 0; return { div: { children: [ // This function automatically updates when counter.value changes { h2: { text: () => `Count: ${counter.value}` } }, { button: { text: 'Increment', onclick: () => counter.value++ }}, // This function is independent and only updates when counter.doubled changes { p: { text: () => `Doubled: ${counter.doubled || counter.value * 2}` } }, // Conditional reactive function { p: { text: () => counter.value > 10 ? 'High count!' : 'Low count' }} ] } }; };
Each reactive function tracks its own dependencies and updates independently:
const UserProfile = (props, context) => { const { user, stats } = context.fluentState.getFluentStates(); user.name = user.name || 'Guest'; stats.visits = stats.visits || 0; return { div: { children: [ // Only updates when user.name changes { h1: { text: () => `Welcome, ${user.name}!` } }, // Only updates when stats.visits changes { p: { text: () => `Visits: ${stats.visits}` } }, // Updates when either user.name OR stats.visits changes { p: { text: () => `${user.name} has visited ${stats.visits} times` }}, // Static elements never update { button: { text: 'Static Button' } } ] } }; };
Create computed values that automatically recalculate:
const ShoppingCart = (props, context) => { const { cart } = context.fluentState.getFluentStates(); cart.items = cart.items || []; return { div: { children: [ // This computation runs only when cart.items changes { p: { text: () => { const total = cart.items.reduce((sum, item) => sum + (item.price * item.quantity), 0 ); return `Total: ${total.toFixed(2)}`; } }}, // Independent computation { p: { text: () => { const itemCount = cart.items.length; return `${itemCount} item${itemCount !== 1 ? 's' : ''} in cart`; } }}, // Reactive list - each item updates independently () => cart.items.map(item => ({ div: { key: item.id, children: [ // Only updates when this specific item.name changes { span: { text: () => item.name } }, // Only updates when this specific item.quantity changes { span: { text: () => `Qty: ${item.quantity}` } } ] } })) ] } }; };
State changes automatically update reactive functions across different components:
const Header = (props, context) => { const { user } = context.fluentState.getFluentStates(); return { header: { children: [ // This reactive function updates when user.name changes { span: { text: () => `Logged in as: ${user.name || 'Guest'}` } }, { button: { text: 'Login', onclick: () => user.name = 'John Doe' }} ] } }; }; const Sidebar = (props, context) => { const { user } = context.fluentState.getFluentStates(); return { aside: { // This reactive function automatically updates when user.name // changes in the Header component - no component re-rendering needed text: () => user.name ? `Welcome ${user.name}` : 'Please log in' } }; };
Use .x to read/write state without creating reactive dependencies:
const AnalyticsComponent = (props, context) => { const { analytics, ui } = context.fluentState.getFluentStates(); const trackEvent = (eventName) => { // These operations don't create reactive dependencies // No functions will be re-executed when these change analytics.x.events = analytics.x.events || []; analytics.x.events.push({ name: eventName, timestamp: Date.now() }); analytics.x.totalEvents = (analytics.x.totalEvents || 0) + 1; }; return { div: { children: [ // This IS reactive - function will re-execute when ui.eventCount changes { p: { text: () => `UI Event Count: ${ui.eventCount || 0}` } }, { button: { text: 'Track Event', onclick: () => { trackEvent('button_click'); // This creates reactive dependency - ui functions will update ui.eventCount = (ui.eventCount || 0) + 1; } }}, // This reactive function accesses analytics normally // It WILL update when analytics.displayTotal changes { p: { text: () => `Display Total: ${analytics.displayTotal || 0}` } }, { button: { text: 'Update Display', onclick: () => { // Copy non-reactive data to reactive field analytics.displayTotal = analytics.x.totalEvents || 0; } }} ] } }; };
Subscribe to state changes for side effects that don't involve the UI:
const NotificationSystem = (props, context) => { const { user, notifications } = context.fluentState.getFluentStates(); return { onMount: () => { // Subscribe to user changes for side effects const unsubscribe = user.subscribe((userData, oldData, changedPath) => { // This runs when user data changes, but doesn't affect reactive functions if (userData.name !== oldData?.name) { // Log to analytics (non-reactive) analytics.x.userChanges = analytics.x.userChanges || []; analytics.x.userChanges.push({ from: oldData?.name, to: userData.name, timestamp: Date.now() }); // Update notifications (reactive - UI functions will update) notifications.items = notifications.items || []; notifications.items.push({ message: `Welcome ${userData.name}!`, timestamp: Date.now() }); } }); return unsubscribe; }, render: () => ({ div: { children: [ // This reactive function updates when notifications.items changes { p: { text: () => `Notifications: ${notifications.items?.length || 0}` } }, // This reactive function shows latest notification () => { const latest = notifications.items?.[notifications.items.length - 1]; return latest ? { p: { text: latest.message } } : null; } ] } }) }; };
FluentState provides helpful utility methods:
const UtilityExample = (props, context) => { const { data } = context.fluentState.getFluentStates(); return { div: { children: [ { button: { text: 'Check if exists', onclick: () => { if (data.user.exists()) { console.log('User data exists'); } } }}, { button: { text: 'Get raw value', onclick: () => { const rawData = data.raw(); console.log('Raw state:', rawData); } }}, { button: { text: 'Clear data', onclick: () => data.clear() }}, { button: { text: 'Update multiple', onclick: () => { data.user.update({ name: 'John', email: 'john@example.com', lastLogin: Date.now() }); } }} ] } }; };
// ✅ Excellent - Complete state structure defined upfront const juris = new Juris({ states: { user: { authenticated: false, profile: { name: 'Guest', email: '' }, preferences: { theme: 'light', notifications: true } }, app: { initialized: true, loading: false, errors: [] }, todos: { items: [], filter: 'all', newTodo: '' } }, // ... rest of config }); // Components are clean and predictable const UserComponent = (props, context) => { const { user } = context.fluentState.getFluentStates(); // State structure is guaranteed - no initialization needed return { div: { // These paths are guaranteed to exist text: () => `${user.profile.name} (${user.preferences.theme} theme)` } }; }; // ❌ Avoid - Component-level initialization creates unpredictability const BadUserComponent = (props, context) => { const { user } = context.fluentState.getFluentStates(); // Don't do this - initialization scattered everywhere user.profile = user.profile || {}; user.profile.name = user.profile.name || 'Guest'; user.preferences = user.preferences || {}; // Component logic is unclear and error-prone return { /* component */ }; };
// Good: Clear domain separation for precise reactive dependencies const { auth, products, cart, ui } = context.fluentState.getFluentStates(); // Reactive functions only depend on their specific domain: // text: () => auth.user.name // Only updates when auth changes // text: () => products.items.length // Only updates when products change // text: () => cart.totals.total // Only updates when cart changes // class: () => ui.theme // Only updates when ui changes // Less optimal: Generic organization const { $, data, state } = context.fluentState.getFluentStates(); // Reactive functions may have broader dependencies: // text: () => $.user.name // Could be affected by any $ change
const Component = (props, context) => { const { data, ui } = context.fluentState.getFluentStates(); return { div: { children: [ // Good: Minimal reactive dependencies { p: { text: () => `${data.items.length} items` } }, // Good: Conditional reactive function () => ui.loading ? { div: { text: 'Loading...' } } : { div: { text: 'Ready' } }, // Good: Static content (no reactive dependencies) { h1: { text: 'My App' } }, // Be careful: This function depends on both data.items AND ui.theme { p: { text: () => `${data.items.length} items (${ui.theme} theme)` }} ] } }; };
// Good: Minimal reactive dependencies const UserInfo = (props, context) => { const { user } = context.fluentState.getFluentStates(); return { div: { children: [ // This only updates when user.profile.name changes { h2: { text: () => user.profile.name } }, // This only updates when user.profile.email changes { p: { text: () => user.profile.email } }, // This updates when either user.profile.name OR user.profile.email changes { p: { text: () => `${user.profile.name} (${user.profile.email})` } } ] } }; }; // Less optimal: Broad reactive dependencies const UserInfoBroad = (props, context) => { const { user } = context.fluentState.getFluentStates(); return { div: { children: [ // This updates whenever ANYTHING in user changes { div: { text: () => JSON.stringify(user.raw()) } } ] } }; };
// Good: Use .x for data that shouldn't trigger reactive updates const Analytics = (props, context) => { const { analytics, ui } = context.fluentState.getFluentStates(); const trackEvent = (event) => { // This doesn't create reactive dependencies analytics.x.events = analytics.x.events || []; analytics.x.events.push({ event, timestamp: Date.now() }); analytics.x.pageViews++; }; return { div: { children: [ // This creates reactive dependency on ui.displayCount { p: { text: () => `Display Count: ${ui.displayCount || 0}` } }, { button: { text: 'Track Event', onclick: () => { trackEvent('button_click'); // This triggers reactive functions to update ui.displayCount = (ui.displayCount || 0) + 1; } }} ] } }; };
// Good: Clear separation of concerns with base state defined in config const juris = new Juris({ states: { todos: { items: [], stats: { total: 0, completed: 0 } }, ui: { filter: 'all', theme: 'light' } } // ... rest of config }); const TodoApp = (props, context) => { const { todos, ui } = context.fluentState.getFluentStates(); return { div: { children: [ // Header - only updates when todos.stats change { TodoHeader: {} }, // Accesses todos.stats directly // Main list - only updates when todos.items change { TodoList: {} }, // Accesses todos.items directly // Footer - only updates when ui state changes { TodoFooter: {} } // Accesses ui directly ] } }; }; const TodoHeader = (props, context) => { const { todos } = context.fluentState.getFluentStates(); return { header: { // Only this function updates when todos.stats.completed changes text: () => `${todos.stats.completed} completed tasks` } }; };
Returns a proxy object for accessing state with destructuring support.
Usage:
const { $, user, config } = context.fluentState.getFluentStates();
user.name = 'John'; // Simple assignment user.profile.age = 25; // Nested assignment $.globalCounter = 100; // Root proxy assignment
const name = user.name; // Direct access const age = user.profile.age; // Nested access
text: () => user.name // Reactive in component functions text: () => `Hello ${user.name}!` // Reactive with template
user.x.name = 'John'; // Set without triggering re-renders const name = user.x.name; // Get without subscribing
items.push(newItem); // Add item items.pop(); // Remove last item items.splice(index, 1); // Remove at index items.length = 0; // Clear array
user.exists() // Check if path has value user.raw() // Get raw JavaScript value user.clear() // Set to null user.update({ name: 'John' }) // Merge properties user.subscribe(callback) // Subscribe to changes
const TodoApp = (props, context) => { const { todos } = context.fluentState.getFluentStates(); // Initialize state todos.list = todos.list || []; todos.newTodo = todos.newTodo || ''; todos.filter = todos.filter || 'all'; const addTodo = () => { if (todos.newTodo.trim()) { todos.list.push({ id: Date.now(), text: todos.newTodo.trim(), completed: false }); todos.newTodo = ''; } }; const filteredTodos = () => { switch (todos.filter) { case 'active': return todos.list.filter(t => !t.completed); case 'completed': return todos.list.filter(t => t.completed); default: return todos.list; } }; return { div: { class: 'todo-app', children: [ { h1: { text: 'Todo List' } }, // Add todo input { div: { class: 'add-todo', children: [ { input: { type: 'text', value: () => todos.newTodo, placeholder: 'What needs to be done?', oninput: (e) => todos.newTodo = e.target.value, onkeypress: (e) => e.key === 'Enter' && addTodo() }}, { button: { text: 'Add', onclick: addTodo } } ] }}, // Filter buttons { div: { class: 'filters', children: ['all', 'active', 'completed'].map(filter => ({ button: { text: filter, class: () => todos.filter === filter ? 'active' : '', onclick: () => todos.filter = filter } })) }}, // Todo list { div: { class: 'todo-list', children: () => filteredTodos().map(todo => ({ div: { key: todo.id, class: () => todo.completed ? 'completed' : '', children: [ { input: { type: 'checkbox', checked: () => todo.completed, onchange: (e) => todo.completed = e.target.checked }}, { span: { text: todo.text } }, { button: { text: ×ばつ', onclick: () => { const index = todos.list.findIndex(t => t.id === todo.id); todos.list.splice(index, 1); } }} ] } })) }}, // Status bar { p: { text: () => { const remaining = todos.list.filter(t => !t.completed).length; return `${remaining} item${remaining !== 1 ? 's' : ''} left`; } }} ] } }; };
const UserDashboard = (props, context) => { const { user, stats, ui } = context.fluentState.getFluentStates(); // Initialize state user.profile = user.profile || { name: 'Guest', email: '' }; stats.visits = stats.visits || 0; stats.lastLogin = stats.lastLogin || null; ui.darkMode = ui.darkMode ?? false; const login = () => { user.profile.name = 'John Doe'; user.profile.email = 'john@example.com'; stats.visits++; stats.lastLogin = Date.now(); }; const logout = () => { user.profile.name = 'Guest'; user.profile.email = ''; }; return { div: { class: () => `dashboard ${ui.darkMode ? 'dark' : 'light'}`, children: [ { header: { children: [ { h1: { text: () => `Welcome, ${user.profile.name}!` } }, { button: { text: () => ui.darkMode ? '☀️ Light' : '🌙 Dark', onclick: () => ui.darkMode = !ui.darkMode }} ] }}, { main: { children: [ { section: { class: 'user-info', children: [ { h2: { text: 'Profile' } }, { p: { text: () => `Email: ${user.profile.email || 'Not set'}` } }, { p: { text: () => `Visits: ${stats.visits}` } }, { p: { text: () => stats.lastLogin ? `Last login: ${new Date(stats.lastLogin).toLocaleString()}` : 'Never logged in' }} ] }}, { section: { class: 'actions', children: [ () => user.profile.name === 'Guest' ? { button: { text: 'Login', onclick: login } } : { button: { text: 'Logout', onclick: logout } } ] }} ] }} ] } }; };
FluentState transforms state management in Juris applications by providing an intuitive, powerful API that feels like working with regular JavaScript objects. With automatic reactivity, flexible access patterns, and minimal setup, you can build complex, responsive applications with ease.
- Intuitive API: State access feels like regular JavaScript
- Automatic Reactivity: UI updates automatically when state changes
- Flexible Patterns: Choose the access pattern that fits your component
- Zero Configuration: Start using state immediately after setup
- Powerful Features: Non-reactive access, subscriptions, utilities, and more
Start building reactive applications with FluentState today!