DEV Community

jurisauthor
jurisauthor

Posted on

Juris Web Framework Developer Reference Guide

Image description

Photo by luis gomes: https://www.pexels.com/photo/close-up-photo-of-programming-of-codes-546819/

Version 0.8.0 - The only Non-Blocking Reactive Framework for JavaScript

⚠️ Important: Follow This Reference Guide

This guide contains the canonical patterns and conventions for Juris development. Following these patterns is essential to:

  • Prevent spaghetti code - Juris's flexibility can lead to inconsistent patterns if not properly structured
  • Maintain code readability - Consistent VDOM syntax, component structure, and state organization
  • Ensure optimal performance - Proper use of reactivity, batching, and the "ignore" pattern
  • Enable team collaboration - Standardized approaches that all developers can understand and maintain
  • Leverage framework features - Correct usage of headless components, DOM enhancement, and state propagation

Key Rules:

  1. Use labeled closing brackets for nested structures (}//div, }//button)
  2. Prefer semantic HTML tags over unnecessary CSS classes (div, button, ul not div.wrapper unless styling needed)
  3. Use either text OR children - the last defined property wins
  4. Structure state paths logically (user.profile.name or userName - both are fine, use nesting when it makes sense)
  5. Use services for stateless utilities, headless components for stateful business logic
  6. Leverage headless components for business logic, regular components for UI
  7. Use DOM enhancement when integrating with existing HTML/libraries

Anti-patterns to avoid:

  • Mixing business logic in UI components
  • Deeply nested state objects
  • Unnecessary re-renders (use "ignore" pattern)
  • Complex conditional logic in reactive functions
  • Manual DOM manipulation outside Juris

Follow these patterns religiously to build maintainable, performant Juris applications.

Quick Start

Basic Setup

<!DOCTYPE html> <html> <head> <script src="https://unpkg.com/juris@0.8.0/juris.js"></script> </head> <body> <div id="app"></div> <script> const app = new Juris({ states: { count: 0 }, layout: { div: { text: () => app.getState('count', 0), children: [ { button: { text: '+', onclick: () => app.setState('count', app.getState('count') + 1) } }, { button: { text: '-', onclick: () => app.setState('count', app.getState('count') - 1) } } ] }//div } }); app.render(); </script> </body> </html> 
Enter fullscreen mode Exit fullscreen mode

Component-Based App

const app = new Juris({ states: { todos: [] }, components: { TodoApp: (props, { getState, setState }) => ({ div: { children: [ { TodoInput: {} }, { TodoList: {} } ] }//div }), TodoInput: (props, { getState, setState }) => ({ input: { type: 'text', placeholder: 'Add todo...', onkeypress: (e) => { if (e.key === 'Enter' && e.target.value.trim()) { const todos = getState('todos', []); setState('todos', [...todos, { id: Date.now(), text: e.target.value.trim() }]); e.target.value = ''; } } }//input }), TodoList: (props, { getState }) => ({ ul: { children: () => getState('todos', []).map(todo => ({ li: { text: todo.text, key: todo.id } })) }//ul }) }, layout: { TodoApp: {} } }); app.render(); 
Enter fullscreen mode Exit fullscreen mode

Core Concepts

Reactivity

Juris uses automatic dependency detection. When you call getState() inside reactive functions, it automatically subscribes to changes:

// Reactive text - updates when 'user.name' changes text: () => getState('user.name', 'Anonymous') // Reactive children - updates when 'items' changes  children: () => getState('items', []).map(item => ({ li: { text: item.name } })) // Reactive attributes className: () => getState('isActive') ? 'active' : 'inactive' 
Enter fullscreen mode Exit fullscreen mode

State Change Propagation

Juris implements hierarchical state propagation - when you change a state path, it automatically notifies all related subscribers:

// Given this state structure: const state = { user: { profile: { name: 'John', email: 'john@example.com' }, settings: { theme: 'dark' } } }; // When you call: setState('user.profile.name', 'Jane'); // Juris automatically triggers updates for subscribers to: // 1. 'user.profile.name' (exact match) // 2. 'user.profile' (parent path)  // 3. 'user' (grandparent path) // 4. Any child paths (if they existed) 
Enter fullscreen mode Exit fullscreen mode

Dependency Re-discovery: Reactive functions can discover new dependencies as they run:

const ConditionalComponent = (props, { getState }) => ({ div: { text: () => { const showDetails = getState('ui.showDetails', false); if (showDetails) { // When showDetails becomes true, Juris automatically  // subscribes to 'user.name' for future updates return getState('user.name', 'No name'); } return 'Click to show details'; } }//div }); // Initially subscribed to: ['ui.showDetails'] // After showDetails becomes true: ['ui.showDetails', 'user.name'] // Juris handles this subscription change automatically 
Enter fullscreen mode Exit fullscreen mode

Propagation in Action:

// Multiple components can subscribe to different levels const UserProfile = (props, { getState }) => ({ div: { // Subscribes to 'user.profile.name' text: () => `Name: ${getState('user.profile.name', '')}` }//div }); const UserCard = (props, { getState }) => ({ div: { // Subscribes to entire 'user.profile' object text: () => { const profile = getState('user.profile', {}); return `${profile.name} (${profile.email})`; } }//div }); const UserSection = (props, { getState }) => ({ div: { // Subscribes to entire 'user' object children: () => { const user = getState('user', {}); return [ { UserProfile: {} }, { UserCard: {} }, { div: { text: `Theme: ${user.settings?.theme}` } } ]; } }//div }); // When you call setState('user.profile.name', 'Alice'): // - UserProfile updates (direct subscription) // - UserCard updates (parent subscription)  // - UserSection updates (grandparent subscription) // All automatically, no manual event handling needed! 
Enter fullscreen mode Exit fullscreen mode

VDOM Structure

Juris uses a lean object syntax for virtual DOM:

// Single element { tagName: { prop: value } } // With children { div: { children: [ { h1: { text: 'Title' } }, { p: { text: 'Content' } } ] }//div } // Use CSS selectors only when you need specific classes/IDs { 'div.container': { children: [ { 'h1#main-title': { text: 'Main Title' } }, { 'p.highlight': { text: 'Important content' } } ] }//div.container } // Reactive properties use functions { div: { text: () => getState('message'), style: () => ({ color: getState('theme.color') }), children: () => getState('items', []).map(item => ({ span: { text: item.name } })) }//div } 
Enter fullscreen mode Exit fullscreen mode

Text vs Children Precedence

Important: When both text and children are defined on the same element, the last one defined wins:

// Text wins (defined last) { div: { children: [{ button: { text: 'Click me' } }], text: 'Override text' // This wins - shows "Override text" }//div } // Children win (defined last)  { div: { text: 'Will be replaced', children: [{ button: { text: 'Click me' } }] // This wins - shows button }//div } // Reactive example - dynamic switching { div: { children: [ { p: { text: 'Default content' } }, { button: { text: 'Action' } } ], text: () => { const loading = getState('isLoading', false); if (loading) { return 'Loading...'; // Replaces children when loading } // Return undefined to keep children return undefined; } }//div } // Best practice: Use either text OR children consistently { div: { children: () => { const loading = getState('isLoading', false); if (loading) { return [{ span: { text: 'Loading...' } }]; } return [ { p: { text: 'Content loaded' } }, { button: { text: 'Action' } } ]; } }//div } 
Enter fullscreen mode Exit fullscreen mode

State Management

Basic State Operations

// Initialize with default state const app = new Juris({ states: { user: { name: 'John', age: 30 }, settings: { theme: 'dark' }, items: [] } }); // Get state with default fallback const name = app.getState('user.name', 'Unknown'); const theme = app.getState('settings.theme', 'light'); // Set state (triggers reactivity) app.setState('user.name', 'Jane'); app.setState('settings.theme', 'light'); app.setState('items', [...app.getState('items', []), newItem]); 
Enter fullscreen mode Exit fullscreen mode

State Subscriptions

// Subscribe to specific path const unsubscribe = app.subscribe('user.name', (newValue, oldValue, path) => { console.log(`${path} changed from ${oldValue} to ${newValue}`); }); // Subscribe to exact path only (no children) const unsubscribeExact = app.subscribeExact('user', (newValue, oldValue) => { console.log('User object changed', newValue); }); // Unsubscribe unsubscribe(); 
Enter fullscreen mode Exit fullscreen mode

Batched Updates

// Manual batching for performance app.stateManager.beginBatch(); app.setState('user.name', 'John'); app.setState('user.age', 31); app.setState('user.email', 'john@example.com'); app.stateManager.endBatch(); // Single render update // Check if batching is active if (app.stateManager.isBatchingActive()) { console.log('Currently batching updates'); } 
Enter fullscreen mode Exit fullscreen mode

Non-Reactive State Access

// Skip reactivity subscription (3rd parameter = false) const value = getState('some.path', defaultValue, false); 
Enter fullscreen mode Exit fullscreen mode

Component System

Component Registration

// Register individual component app.registerComponent('MyButton', (props, context) => ({ button: { text: props.label || 'Click me', className: props.variant || 'default', onclick: props.onClick || (() => {}) }//button })); // Register multiple components const app = new Juris({ components: { Header: (props, { getState }) => ({ header: { h1: { text: () => getState('app.title', 'My App') } }//header }), Counter: (props, { getState, setState }) => { const count = () => getState('counter.value', 0); return { div: { children: [ { span: { text: () => `Count: ${count()}` } }, { button: { text: '+', onclick: () => setState('counter.value', count() + 1) } }, { button: { text: '-', onclick: () => setState('counter.value', count() - 1) } } ] }//div }; } } }); 
Enter fullscreen mode Exit fullscreen mode

Component Props

// Component with props const UserCard = (props, { getState }) => ({ div: { children: [ { img: { src: props.avatar, alt: 'Avatar' } }, { h3: { text: props.name } }, { p: { text: props.email } }, { button: { text: 'Follow', onclick: props.onFollow, disabled: props.isFollowing }}//button ] }//div }); // Usage with props { UserCard: { name: 'John Doe', email: 'john@example.com', avatar: '/avatar.jpg', isFollowing: () => getState('following.includes', false), onFollow: () => setState('following', [...getState('following', []), userId]) }//UserCard } 
Enter fullscreen mode Exit fullscreen mode

Component Lifecycle

const LifecycleComponent = (props, context) => { return { // Component lifecycle hooks hooks: { onMount: () => { console.log('Component mounted'); // Setup event listeners, timers, etc. }, onUpdate: (oldProps, newProps) => { console.log('Props changed', { oldProps, newProps }); }, onUnmount: () => { console.log('Component unmounting'); // Cleanup resources } }, // Component API (accessible to parent) api: { focus: () => element.querySelector('input')?.focus(), getValue: () => getState('local.value', '') }, // Render function render: () => ({ div: { text: 'Lifecycle component' } }) }; }; 
Enter fullscreen mode Exit fullscreen mode

Local Component State

const StatefulComponent = (props, { newState }) => { const [getCount, setCount] = newState('count', 0); const [getText, setText] = newState('text', ''); return { div: { children: [ { input: { value: () => getText(), oninput: (e) => setText(e.target.value) }},//input { button: { text: () => `Clicked ${getCount()} times`, onclick: () => setCount(getCount() + 1) }}//button ] }//div }; }; 
Enter fullscreen mode Exit fullscreen mode

DOM Enhancement

Basic Enhancement

// Enhance existing DOM elements app.enhance('.my-button', { className: () => getState('theme') === 'dark' ? 'btn-dark' : 'btn-light', onclick: () => setState('clicks', getState('clicks', 0) + 1), text: () => `Clicked ${getState('clicks', 0)} times` }); // Enhance with function-based definition app.enhance('.counter', (context) => { const { getState, setState, element } = context; return { text: () => getState('counter.value', 0), style: () => ({ color: getState('counter.value', 0) > 10 ? 'red' : 'blue' }), onclick: () => setState('counter.value', getState('counter.value', 0) + 1) }; }); // Enhancement with services access app.enhance('.api-button', (context) => { const { api, storage, setState } = context; // Services from config return { text: 'Load Data', onclick: async () => { setState('loading', true); try { const data = await api.get('/api/users'); storage.save('users', data); setState('users', data); } catch (error) { setState('error', error.message); } finally { setState('loading', false); } }, disabled: () => getState('loading', false) }; }); // Enhancement with headless component access (direct from context) app.enhance('.notification-trigger', (context) => { // Headless APIs are available directly from context const { NotificationManager } = context; return { text: 'Show Notification', onclick: () => { NotificationManager.show({ type: 'success', message: 'Enhancement triggered!', duration: 3000 }); } }; }); 
Enter fullscreen mode Exit fullscreen mode

Selector-Based Enhancement

// Enhance containers with multiple selectors app.enhance('.dashboard', { selectors: { '.metric': { text: () => getState('metrics.revenue', '$0'), className: () => getState('metrics.trend') === 'up' ? 'positive' : 'negative' }, '.chart': (context) => ({ innerHTML: () => `<canvas data-value="${getState('metrics.data', [])}"></canvas>` }), '.refresh-btn': { onclick: () => { setState('loading', true); fetchMetrics().then(data => { setState('metrics', data); setState('loading', false); }); }, disabled: () => getState('loading', false) } } }); // Selector enhancement with services and headless components app.enhance('.user-dashboard', { // Container-level enhancement className: () => getState('user.role', 'guest'), selectors: { '.user-avatar': (context) => { const { api, storage } = context; // Services return { src: () => getState('user.avatar', '/default-avatar.png'), onclick: async () => { const newAvatar = await api.uploadAvatar(); storage.save('userAvatar', newAvatar); setState('user.avatar', newAvatar); } }; }, '.notification-bell': (context) => { // Direct access to headless component API const { NotificationManager } = context; return { text: () => { const count = NotificationManager.getUnreadCount(); return count > 0 ? count.toString() : ''; }, className: () => NotificationManager.hasUnread() ? 'has-notifications' : '', onclick: () => NotificationManager.markAllAsRead() }; }, '.sync-status': (context) => { const { SyncManager, api } = context; return { text: () => { const status = SyncManager.getStatus(); return status === 'syncing' ? 'Syncing...' : status === 'error' ? 'Sync Failed' : 'Synced'; }, className: () => `sync-${SyncManager.getStatus()}`, onclick: () => SyncManager.forceSync() }; }, '.data-export': (context) => { const { api, storage, DataManager } = context; return { text: 'Export Data', onclick: async () => { setState('exporting', true); try { const data = DataManager.getAllData(); const blob = await api.exportToCSV(data); const url = URL.createObjectURL(blob); // Create download link const a = document.createElement('a'); a.href = url; a.download = 'data-export.csv'; a.click(); storage.save('lastExport', Date.now()); } finally { setState('exporting', false); } }, disabled: () => getState('exporting', false) }; } } }); 
Enter fullscreen mode Exit fullscreen mode

Enhancement Options

app.enhance('.auto-update', definition, { debounceMs: 100, // Debounce DOM mutations batchUpdates: true, // Batch multiple updates observeSubtree: true, // Watch for nested changes observeChildList: true, // Watch for added/removed elements observeNewElements: true, // Auto-enhance new elements onEnhanced: (element, context) => { console.log('Enhanced:', element); } }); 
Enter fullscreen mode Exit fullscreen mode

Template System

Template Syntax

<template data-component="UserProfile" data-context="setState, getState"> <script> const user = () => getState('user', {}); const updateUser = (field, value) => setState(`user.${field}`, value); </script> <div class="profile"> <img src={()=>user().avatar} alt="Avatar" /> <h2>{()=>user().name}</h2> <input value={()=>user().email} oninput={(e)=>updateUser('email', e.target.value)} /> <div class={()=>user().isOnline ? 'online' : 'offline'}> {text: ()=>user().isOnline ? 'Online' : 'Offline'} </div> </div> </template> 
Enter fullscreen mode Exit fullscreen mode

Reactive Template Elements

<template data-component="TodoList" data-context="getState, setState"> <script> const todos = () => getState('todos', []); const addTodo = (text) => setState('todos', [...todos(), { id: Date.now(), text }]); </script> <div> <input onkeypress={(e)=>{ if(e.key==='Enter') { addTodo(e.target.value); e.target.value = ''; } }} /> <ul> {children: ()=>todos().map(todo => ({ li: { text: todo.text, key: todo.id } }))} </ul> </div> </template> 
Enter fullscreen mode Exit fullscreen mode

Auto-Compilation

// Templates auto-compile by default const app = new Juris({ autoCompileTemplates: true, // default states: { user: { name: 'John' } } }); // Manual compilation app.compileTemplates(); // Disable auto-compilation const app = new Juris({ autoCompileTemplates: false }); 
Enter fullscreen mode Exit fullscreen mode

Headless Components

Basic Headless Component

// Register headless component (no DOM) app.registerHeadlessComponent('DataManager', (props, context) => { const { getState, setState, subscribe } = context; // Background logic const fetchData = async () => { setState('loading', true); try { const data = await fetch('/api/data').then(r => r.json()); setState('data', data); } finally { setState('loading', false); } }; return { // Lifecycle hooks hooks: { onRegister: () => { console.log('DataManager registered'); fetchData(); // Initial load // Auto-refresh every 30 seconds setInterval(fetchData, 30000); }, onUnregister: () => { console.log('DataManager cleanup'); } }, // Public API api: { refresh: fetchData, getData: () => getState('data', []), isLoading: () => getState('loading', false) } }; }); // Initialize headless component app.initializeHeadlessComponent('DataManager'); // Access headless API in regular components const MyComponent = (props, { components }) => ({ div: { children: [ { button: { text: 'Refresh Data', onclick: () => components.getHeadlessAPI('DataManager').refresh() } }, { div: { text: () => components.getHeadlessAPI('DataManager').isLoading() ? 'Loading...' : 'Ready' } } ] } }); 
Enter fullscreen mode Exit fullscreen mode

Auto-Init Headless Components

const app = new Juris({ headlessComponents: { // Auto-initialize on startup AuthManager: { fn: (props, context) => ({ api: { login: (credentials) => { /* login logic */ }, logout: () => { /* logout logic */ }, isAuthenticated: () => context.getState('auth.isLoggedIn', false) } }), options: { autoInit: true } }, // Simple function (no auto-init) CacheManager: (props, context) => ({ api: { set: (key, value) => context.setState(`cache.${key}`, value), get: (key) => context.getState(`cache.${key}`) } }) } }); 
Enter fullscreen mode Exit fullscreen mode

Async Handling

Async Props

// Components handle async props automatically const AsyncComponent = (props, context) => ({ div: { // Async text - shows loading state automatically text: fetch('/api/message').then(r => r.text()), // Async children children: fetch('/api/items').then(r => r.json()).then(items => items.map(item => ({ li: { text: item.name } })) ), // Async styles style: fetch('/api/theme').then(r => r.json()) } }); // Mixed sync/async props { div: { className: 'container', // sync text: () => getState('title'), // reactive style: fetchUserTheme(), // async promise children: [ { span: { text: 'Static content' } }, { span: { text: fetchDynamicContent() } } // async ] } } 
Enter fullscreen mode Exit fullscreen mode

Async State Updates

// Async setState setState('user', fetchUserData()); // Promise resolves automatically // Manual async handling const loadUser = async (userId) => { setState('loading', true); try { const user = await fetch(`/api/users/${userId}`).then(r => r.json()); setState('user', user); } catch (error) { setState('error', error.message); } finally { setState('loading', false); } }; 
Enter fullscreen mode Exit fullscreen mode

Promise Tracking

// Track all promises for SSR/hydration startTracking(); // Render with async content app.render(); // Wait for all promises to resolve onAllComplete(() => { console.log('All async operations completed'); stopTracking(); }); 
Enter fullscreen mode Exit fullscreen mode

Advanced Features

Server-Side Rendering (SSR)

// SSR-ready configuration const app = new Juris({ states: { isHydration: true }, layout: { App: {} } }); // Render with hydration mode app.render(); // Automatically handles SSR hydration 
Enter fullscreen mode Exit fullscreen mode

Render Modes

// Fine-grained reactivity (default) - immediate updates app.setRenderMode('fine-grained'); // Batch mode - batched updates for performance app.setRenderMode('batch'); // Check current mode if (app.isFineGrained()) { console.log('Using fine-grained rendering'); } // Set mode in constructor const app = new Juris({ renderMode: 'batch' // or 'fine-grained' }); 
Enter fullscreen mode Exit fullscreen mode

Middleware

const app = new Juris({ middleware: [ // Logging middleware ({ path, oldValue, newValue, context }) => { console.log(`State change: ${path}`, { oldValue, newValue }); return newValue; // Return undefined to use original value }, // Validation middleware ({ path, newValue }) => { if (path === 'user.age' && newValue < 0) { console.warn('Age cannot be negative'); return 0; // Override with valid value } return newValue; }, // Persistence middleware ({ path, newValue }) => { if (path.startsWith('user.')) { localStorage.setItem('user', JSON.stringify(newValue)); } return newValue; } ] }); 
Enter fullscreen mode Exit fullscreen mode

Service Injection

const app = new Juris({ services: { api: { get: (url) => fetch(url).then(r => r.json()), post: (url, data) => fetch(url, { method: 'POST', body: JSON.stringify(data), headers: { 'Content-Type': 'application/json' } }) }, storage: { save: (key, value) => localStorage.setItem(key, JSON.stringify(value)), load: (key) => JSON.parse(localStorage.getItem(key) || 'null') } } }); // Use services in components const MyComponent = (props, { api, storage }) => ({ button: { text: 'Save Data', onclick: async () => { const data = await api.get('/api/data'); storage.save('backup', data); } } }); 
Enter fullscreen mode Exit fullscreen mode

Performance Optimization

Element Recycling

// DOM renderer automatically recycles elements // No configuration needed - handles pool management // Clear caches when needed app.domRenderer.clearAsyncCache(); app.componentManager.clearAsyncPropsCache(); 
Enter fullscreen mode Exit fullscreen mode

Batched DOM Updates

// Manual batching for multiple state changes app.stateManager.beginBatch(); // Multiple state updates setState('user.name', 'John'); setState('user.email', 'john@example.com'); setState('user.age', 30); // Single DOM update app.stateManager.endBatch(); 
Enter fullscreen mode Exit fullscreen mode

Efficient List Rendering

// Use keys for efficient list updates children: () => getState('items', []).map(item => ({ li: { text: item.name, key: item.id // Important for performance } })) // Avoid recreating objects in render functions const renderItem = (item) => ({ li: { text: item.name, key: item.id } }); children: () => getState('items', []).map(renderItem) // Use "ignore" pattern for structural optimization app.registerComponent('ListItem', (props, { getState }) => ({ li: { className: () => getState(`items.${props.itemId}.status`, 'active'), children: [ { span: { text: () => getState(`items.${props.itemId}.name`, '') } }, { small: { text: () => getState(`items.${props.itemId}.updatedAt`, '') } } ] } })); const OptimizedList = (props, { getState }) => { let lastItemIds = []; return { ul: { children: () => { const items = getState('itemsList', []); // Just the list of IDs const currentItemIds = items.map(item => item.id); // If the list structure (IDs) hasn't changed, skip re-rendering // Individual ListItem components will still update when their data changes if (JSON.stringify(currentItemIds) === JSON.stringify(lastItemIds)) { return "ignore"; } lastItemIds = currentItemIds; return items.map(item => ({ ListItem: { itemId: item.id, key: item.id } })); } } }; }; 
Enter fullscreen mode Exit fullscreen mode

API Reference

Core Instance Methods

const app = new Juris(config); // State Management app.getState(path, defaultValue, track = true) app.setState(path, value, context = {}) app.subscribe(path, callback, hierarchical = true) app.subscribeExact(path, callback) // Component Management  app.registerComponent(name, componentFn) app.registerHeadlessComponent(name, componentFn, options = {}) app.getComponent(name) app.getHeadlessComponent(name) app.initializeHeadlessComponent(name, props = {}) // Rendering app.render(container = '#app') app.setRenderMode('fine-grained' | 'batch') app.getRenderMode() app.isFineGrained() app.isBatchMode() // DOM Enhancement app.enhance(selector, definition, options = {}) app.configureEnhancement(options) app.getEnhancementStats() // Template System app.compileTemplates() // Utilities app.cleanup() app.destroy() app.getHeadlessStatus() 
Enter fullscreen mode Exit fullscreen mode

Context Object

// Available in all components and enhancement functions const context = { // State operations getState(path, defaultValue, track = true), setState(path, value, context = {}), subscribe(path, callback), // Local state (components only) newState(key, initialValue), // Returns [getter, setter] // Services (if configured) ...services, // Headless APIs ...headlessAPIs, // Component utilities components: { register(name, component), registerHeadless(name, component, options), get(name), getHeadless(name), initHeadless(name, props), reinitHeadless(name, props), getHeadlessAPI(name), getAllHeadlessAPIs() }, // Utilities utils: { render(container), cleanup(), forceRender(), setRenderMode(mode), getRenderMode(), isFineGrained(), isBatchMode(), getHeadlessStatus() }, // Framework access juris: app, // Current element (enhancement only) element: domElement, // Environment isSSR: boolean, // Logging logger: { log, warn, error, info, debug, subscribe, unsubscribe } }; 
Enter fullscreen mode Exit fullscreen mode

Configuration Options

const config = { // Initial state states: {}, // State middleware middleware: [], // Root layout component layout: {}, // Component definitions components: {}, // Headless components headlessComponents: {}, // Services for dependency injection services: {}, // Render mode renderMode: 'auto' | 'fine-grained' | 'batch', // Template compilation autoCompileTemplates: true, // Logging level logLevel: 'debug' | 'info' | 'warn' | 'error' }; 
Enter fullscreen mode Exit fullscreen mode

Common Patterns

Form Handling

const LoginForm = (props, { getState, setState }) => { const [getEmail, setEmail] = newState('email', ''); const [getPassword, setPassword] = newState('password', ''); const handleSubmit = (e) => { e.preventDefault(); setState('auth.isLoading', true); fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: getEmail(), password: getPassword() }) }) .then(r => r.json()) .then(data => { setState('auth.user', data.user); setState('auth.token', data.token); }) .finally(() => { setState('auth.isLoading', false); }); }; return { form: { onsubmit: handleSubmit, children: [ { input: { type: 'email', placeholder: 'Email', value: () => getEmail(), oninput: (e) => setEmail(e.target.value) } }, { input: { type: 'password', placeholder: 'Password', value: () => getPassword(), oninput: (e) => setPassword(e.target.value) } }, { button: { type: 'submit', text: () => getState('auth.isLoading') ? 'Logging in...' : 'Login', disabled: () => getState('auth.isLoading') } } ] } }; }; 
Enter fullscreen mode Exit fullscreen mode

Modal Management

// Headless modal manager app.registerHeadlessComponent('ModalManager', (props, { getState, setState }) => ({ api: { open: (id, props = {}) => setState(`modals.${id}`, { open: true, ...props }), close: (id) => setState(`modals.${id}.open`, false), isOpen: (id) => getState(`modals.${id}.open`, false), getProps: (id) => getState(`modals.${id}`, {}) } })); // Modal component const Modal = (props, { components }) => { const modalManager = components.getHeadlessAPI('ModalManager'); return { div: { className: () => modalManager.isOpen(props.id) ? 'modal open' : 'modal', onclick: (e) => { if (e.target === e.currentTarget) { modalManager.close(props.id); } }, children: () => modalManager.isOpen(props.id) ? [ { div: { className: 'modal-content', children: [ { button: { className: 'close', text: '×', onclick: () => modalManager.close(props.id) } }, props.children ] } } ] : [] } }; }; 
Enter fullscreen mode Exit fullscreen mode

Data Fetching Pattern

// Generic data fetcher headless component app.registerHeadlessComponent('DataFetcher', (props, { getState, setState }) => ({ api: { fetch: async (key, url, options = {}) => { setState(`data.${key}.loading`, true); setState(`data.${key}.error`, null); try { const response = await fetch(url, options); if (!response.ok) throw new Error(`HTTP ${response.status}`); const data = await response.json(); setState(`data.${key}.data`, data); setState(`data.${key}.lastFetch`, Date.now()); } catch (error) { setState(`data.${key}.error`, error.message); } finally { setState(`data.${key}.loading`, false); } }, getData: (key) => getState(`data.${key}.data`), isLoading: (key) => getState(`data.${key}.loading`, false), getError: (key) => getState(`data.${key}.error`), shouldRefetch: (key, maxAge = 300000) => { // 5 minutes const lastFetch = getState(`data.${key}.lastFetch`, 0); return Date.now() - lastFetch > maxAge; } } })); // Usage in component const UserList = (props, { components }) => { const dataFetcher = components.getHeadlessAPI('DataFetcher'); // Auto-fetch on mount useEffect(() => { if (dataFetcher.shouldRefetch('users')) { dataFetcher.fetch('users', '/api/users'); } }); return { div: { children: () => { if (dataFetcher.isLoading('users')) { return [{ div: { text: 'Loading users...' } }]; } if (dataFetcher.getError('users')) { return [{ div: { className: 'error', text: `Error: ${dataFetcher.getError('users')}` } }]; } const users = dataFetcher.getData('users') || []; return users.map(user => ({ div: { key: user.id, className: 'user-card', children: [ { h3: { text: user.name } }, { p: { text: user.email } } ] } })); } } }; }; 
Enter fullscreen mode Exit fullscreen mode

Debugging and Development

State Inspection

// Debug state in console console.log('Current state:', app.stateManager.state); // Monitor all state changes using middleware const debugMiddleware = ({ path, oldValue, newValue }) => { console.log(`State changed: ${path}`, { oldValue, newValue }); return newValue; }; const app = new Juris({ middleware: [debugMiddleware], // ... other config }); // Or subscribe to specific top-level paths app.subscribe('user', (newValue, oldValue, path) => { console.log(`User state changed: ${path}`, { oldValue, newValue }); }); app.subscribe('app', (newValue, oldValue, path) => { console.log(`App state changed: ${path}`, { oldValue, newValue }); }); // Get enhancement statistics console.log('Enhancement stats:', app.getEnhancementStats()); // Get headless component status console.log('Headless status:', app.getHeadlessStatus()); 
Enter fullscreen mode Exit fullscreen mode

Performance Monitoring

// Monitor render performance const startTime = performance.now(); app.render(); console.log(`Render took: ${performance.now() - startTime}ms`); // Monitor state update frequency let updateCount = 0; app.subscribe('', () => { updateCount++; console.log(`State updates: ${updateCount}`); }); 
Enter fullscreen mode Exit fullscreen mode

Error Handling

// Global error handling in middleware const errorHandlingMiddleware = ({ path, oldValue, newValue, context }) => { try { // Validate state updates if (path === 'user.age' && typeof newValue !== 'number') { throw new Error('Age must be a number'); } return newValue; } catch (error) { console.error(`State validation error for ${path}:`, error); // Return old value to prevent invalid change return oldValue; } }; const app = new Juris({ middleware: [errorHandlingMiddleware] }); 
Enter fullscreen mode Exit fullscreen mode

Best Practices

  1. Use keys for list items to enable efficient DOM reconciliation
  2. Batch state updates when making multiple changes
  3. Prefer headless components for complex business logic
  4. Use middleware for cross-cutting concerns like logging and validation
  5. Structure state paths logically (e.g., user.profile.name not userProfileName)
  6. Avoid deep nesting in state objects when possible
  7. Use local component state for UI-only state that doesn't need to be shared
  8. Enhance existing DOM rather than rebuilding when integrating with other libraries
  9. Implement error boundaries in components that fetch data
  10. Use templates for complex HTML structures with light JavaScript logic

Juris v0.8.0 - Built for performance, designed for simplicity.

Top comments (1)

Collapse
 
artyprog profile image
ArtyProg

I really don't understand why they are no comments about Juris.
A web dev blinds, at least for the sake of curiosity ...

Some comments may only be visible to logged-in visitors. Sign in to view all comments.