DEV Community

codeporn
codeporn

Posted on

Juris Developer Reference Guide

Code With Juris

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 (0)