-
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 $ changeconst 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 assignmentconst name = user.name; // Direct access
const age = user.profile.age; // Nested accesstext: () => user.name // Reactive in component functions
text: () => `Hello ${user.name}!` // Reactive with templateuser.x.name = 'John'; // Set without triggering re-renders
const name = user.x.name; // Get without subscribingitems.push(newItem); // Add item
items.pop(); // Remove last item
items.splice(index, 1); // Remove at index
items.length = 0; // Clear arrayuser.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 changesconst 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!