DEV Community

Professional Joe
Professional Joe

Posted on

Juris.js: Understanding Reactivity: The Static vs Reactive Distinction

Juris.js introduces a revolutionary approach to reactivity that's both intuitive and powerful. Unlike other frameworks that require complex dependency tracking systems or explicit subscription mechanisms, Juris uses a simple yet elegant distinction: functions create reactivity, values don't.

This article will walk you through this core concept, from basic understanding to advanced patterns, helping you master one of Juris.js's most innovative features.

The Foundation: Static vs Reactive

In Juris.js, whether something updates automatically depends on a simple question: Is it wrapped in a function?

Static Values (No Updates)

When you return a direct value from getState(), you get a static snapshot that never changes:

const app = new Juris();

app.enhance('#counter', (props, { getState }) => ({
  textContent: getState('count'),  // Static! Gets current value once
  className: 'btn-' + getState('theme')  // Also static
}));
Enter fullscreen mode Exit fullscreen mode

No matter how many times count or theme changes in your application state, this element will display the values that existed when the enhancement first ran.

Reactive Values (Auto-Updates)

Wrap the same code in a function, and it becomes reactive:

app.enhance('#counter', (props, { getState }) => ({
  textContent: () => getState('count'),  // Reactive! Updates when count changes
  className: () => 'btn-' + getState('theme')  // Reactive! Updates when theme changes
}));
Enter fullscreen mode Exit fullscreen mode

Now your element automatically updates whenever count or theme changes. The function wrapper is what creates the subscription.

Real-World Example: Building a User Dashboard

Let's build a user dashboard to see this in action:

// Set up some initial state
const app = new Juris();
app.setState('user.name', 'John Doe');
app.setState('user.status', 'online');
app.setState('notifications.count', 3);
app.setState('theme.mode', 'dark');

// Static approach - values frozen in time
app.enhance('#static-dashboard', (props, { getState }) => ({
  innerHTML: `
    <h1>Welcome ${getState('user.name')}</h1>
    <span class="status ${getState('user.status')}">
      ${getState('user.status')}
    </span>
    <div class="notifications">
      ${getState('notifications.count')} notifications
    </div>
  `
}));

// Reactive approach - updates automatically
app.enhance('#reactive-dashboard', (props, { getState }) => ({
  innerHTML: () => `
    <h1>Welcome ${getState('user.name')}</h1>
    <span class="status ${getState('user.status')}">
      ${getState('user.status')}
    </span>
    <div class="notifications">
      ${getState('notifications.count')} notifications
    </div>
  `,
  className: () => `dashboard ${getState('theme.mode')}-theme`
}));
Enter fullscreen mode Exit fullscreen mode

When you later update the state:

app.setState('user.name', 'Jane Smith');
app.setState('notifications.count', 7);
app.setState('theme.mode', 'light');
Enter fullscreen mode Exit fullscreen mode

The static dashboard remains unchanged, but the reactive dashboard automatically updates to reflect the new values.

Granular Reactivity: Mix and Match

Here's where Juris really shines - you can choose exactly which parts should be reactive:

app.enhance('#mixed-example', (props, { getState, setState }) => ({
  // Static: Set once when component initializes
  id: 'user-' + getState('user.id'),

  // Reactive: Updates when user name changes
  textContent: () => getState('user.name'),

  // Reactive: Dynamic styling based on status
  style: () => ({
    backgroundColor: getState('user.status') === 'online' ? '#22c55e' : '#ef4444',
    opacity: getState('user.isActive') ? 1 : 0.5
  }),

  // Static: Event handler doesn't need to change
  onClick: () => {
    setState('user.status', 'away');
  }
}));
Enter fullscreen mode Exit fullscreen mode

This gives you fine-grained control over performance - only the parts that actually need to update will re-execute.

Advanced Pattern: Computed Values

You can create computed values that depend on multiple state paths:

app.enhance('#computed-example', (props, { getState }) => ({
  textContent: () => {
    const user = getState('user');
    const settings = getState('settings');
    const lastSeen = getState('activity.lastSeen');

    // Complex computation that automatically updates
    // when any dependency changes
    if (user.status === 'online') {
      return `${user.name} is online`;
    } else {
      const timeAgo = Date.now() - lastSeen;
      return `${user.name} was last seen ${formatTimeAgo(timeAgo)}`;
    }
  },

  className: () => {
    const status = getState('user.status');
    const priority = getState('user.priority');
    return `user-card ${status} priority-${priority}`;
  }
}));
Enter fullscreen mode Exit fullscreen mode

Juris automatically tracks all the state paths accessed within these functions and updates only when necessary.

Performance Benefits

This approach offers significant performance advantages:

1. No Unnecessary Re-renders

app.enhance('#efficient-component', (props, { getState }) => ({
  // Only updates when 'title' changes, ignoring other state changes
  textContent: () => getState('title'),

  // Only updates when 'theme' changes
  className: () => `component ${getState('theme')}`,

  // Static - never re-executes
  id: 'component-' + getState('componentId')
}));
Enter fullscreen mode Exit fullscreen mode

2. Surgical Updates

Unlike frameworks that re-render entire components, Juris updates only the specific attributes that have changed:

// When 'user.name' changes, only textContent updates
// When 'theme.color' changes, only style updates
// The onClick handler never re-executes
app.enhance('#surgical-updates', (props, { getState, setState }) => ({
  textContent: () => getState('user.name'),
  style: () => ({ color: getState('theme.color') }),
  onClick: () => setState('clicked', true)
}));
Enter fullscreen mode Exit fullscreen mode

Common Patterns and Best Practices

When to Use Static Values

  • IDs and data attributes that shouldn't change
  • Event handlers (unless they need to close over changing values)
  • Initial configuration values
app.enhance('#best-practices', (props, { getState, setState }) => ({
  // Static: Component ID never changes
  id: 'component-' + getState('componentId'),

  // Static: Data attributes for CSS selectors
  'data-type': getState('component.type'),

  // Reactive: Visual content that should update
  textContent: () => getState('content.title'),

  // Static: Event handler doesn't need state closure
  onClick: () => {
    setState('interactions.clicks', getState('interactions.clicks') + 1);
  }
}));
Enter fullscreen mode Exit fullscreen mode

When to Use Reactive Functions

  • Visual content (text, HTML)
  • Dynamic styling
  • Conditional attributes
  • Computed values
app.enhance('#reactive-patterns', (props, { getState }) => ({
  // Reactive: Content changes
  innerHTML: () => getState('content.html'),

  // Reactive: Conditional classes
  className: () => {
    const classes = ['base'];
    if (getState('state.isActive')) classes.push('active');
    if (getState('state.isSelected')) classes.push('selected');
    return classes.join(' ');
  },

  // Reactive: Dynamic styles
  style: () => ({
    transform: `scale(${getState('ui.scale')})`,
    opacity: getState('ui.visible') ? 1 : 0
  })
}));
Enter fullscreen mode Exit fullscreen mode

Debugging Reactivity

Understanding what's reactive vs static helps with debugging:

app.enhance('#debug-example', (props, { getState }) => ({
  // This will log once and never again (static)
  textContent: console.log('Static evaluation:', getState('debug.value')) || getState('debug.value'),

  // This will log every time debug.value changes (reactive)
  className: () => {
    console.log('Reactive evaluation:', getState('debug.value'));
    return `debug-${getState('debug.value')}`;
  }
}));
Enter fullscreen mode Exit fullscreen mode

Conclusion

Juris.js's static vs reactive distinction is both powerful and intuitive:

  • Values = Static snapshots
  • Functions = Reactive subscriptions

This simple rule gives you:

  • Predictable behavior - Clear when things update
  • Performance control - Choose what should be reactive
  • Clean syntax - No complex subscription APIs
  • Fine-grained updates - Only what changed gets updated

The beauty of this approach is its simplicity. You don't need to learn complex reactivity systems, dependency arrays, or subscription patterns. Just ask yourself: "Should this update when state changes?" If yes, wrap it in a function. If no, use the value directly.

This innovative approach to reactivity makes Juris.js unique among JavaScript frameworks, providing both the power of reactive programming and the simplicity that developers crave.

Top comments (1)

Collapse
 
artydev profile image
artydev

Thank you :-)