Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Juris FluentState Plugin Developer's Guide

jurisauthor edited this page Aug 24, 2025 · 2 revisions

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.

Table of Contents

  1. Quick Setup
  2. Core Concepts
  3. Usage Patterns
  4. Reactivity
  5. Advanced Features
  6. Best Practices
  7. API Reference
  8. Complete Examples

Quick Setup

Simple Configuration with Base State

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 */ }
});

Why Define Base State in Config?

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 }
 }
 }
 // ...
});

Component Access

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' } }
 ]
 }}
 ]
 }
 };
};

Core Concepts

FluentState vs Props: Objects vs Primitives

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}` }
 };
};

Why This Matters

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
 }}
 ]
 }
 };
};

Mental Model

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
};

Common Mistakes to Avoid

❌ 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;

State Access Patterns

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
// }
// }
// }
// }
// }

Usage Patterns

Basic State Operations

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}!` } }
 ]
 }
 };
};

Working with Arrays

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
 }
 }))
 ]
 }
 };
};

State Initialization Best Practices

✅ 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`
 }
 };
};

Advanced State Organization

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
});

Reactivity

How Juris Reactivity Actually Works

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

Reactive Functions

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'
 }}
 ]
 }
 };
};

Fine-Grained Updates

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' } }
 ]
 }
 };
};

Computed Reactive Values

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}` } }
 ]
 }
 }))
 ]
 }
 };
};

Cross-Component Reactivity

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'
 }
 };
};

Advanced Features

Non-Reactive Access

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;
 }
 }}
 ]
 }
 };
};

State Subscriptions

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;
 }
 ]
 }
 })
 };
};

Utility Methods

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()
 });
 }
 }}
 ]
 }
 };
};

Best Practices

1. Always Define Base State in Juris Config

// ✅ 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 */ };
};

2. Organize State by Domain

// 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

3. Use Reactive Functions Appropriately

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)`
 }}
 ]
 }
 };
};

4. Optimize Reactive Dependencies

// 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()) } }
 ]
 }
 };
};

5. Use Non-Reactive Mode for Side Effects

// 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;
 }
 }}
 ]
 }
 };
};

6. Structure Components for Clear Reactive Boundaries

// 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`
 }
 };
};

API Reference

Core Access

context.fluentState.getFluentStates()

Returns a proxy object for accessing state with destructuring support.

Usage:

const { $, user, config } = context.fluentState.getFluentStates();

State Operations

Setting Values

user.name = 'John'; // Simple assignment
user.profile.age = 25; // Nested assignment
$.globalCounter = 100; // Root proxy assignment

Getting Values

const name = user.name; // Direct access
const age = user.profile.age; // Nested access

Reactive Access

text: () => user.name // Reactive in component functions
text: () => `Hello ${user.name}!` // Reactive with template

Non-Reactive Access

user.x.name = 'John'; // Set without triggering re-renders
const name = user.x.name; // Get without subscribing

Array Operations

items.push(newItem); // Add item
items.pop(); // Remove last item
items.splice(index, 1); // Remove at index
items.length = 0; // Clear array

Utility Methods

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

Complete Examples

Todo Application

×ばつ', 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 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`;
 }
 }}
 ]
 }
 };
};

User Dashboard

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 } }
 ]
 }}
 ]
 }}
 ]
 }
 };
};

Conclusion

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.

Key Benefits

  • 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!

Clone this wiki locally

AltStyle によって変換されたページ (->オリジナル) /