Skip to content

Computed Values

Overview

Computed values in neutrix let you create derived state that automatically updates when its dependencies change. Think of them like spreadsheet formulas—they recalc automatically when any dependency changes.

Basic Usage

1. Creating a Computed Value

You can define a computed value on your store by calling store.computed(path, fn), where:

  • path: a unique key identifying the computed result
  • fn: a function that calculates the derived data from your state

Here's a simple example:

typescript
import { createNeutrixStore } from 'neutrix';

const { store, useStore } = createNeutrixStore({
  todos: [
    { id: 1, text: 'Learn Neutrix', completed: false },
    { id: 2, text: 'Write docs', completed: true }
  ]
});

const activeTodos = store.computed(
  'todos.active',
  state => state.todos.filter(todo => !todo.completed)
);

function TodoList() {
  useStore(s => s.todos);

  const active = activeTodos();
  return <div>Active Todos: {active.length}</div>;
}

store.computed('todos.active', ...):

  • Creates a named computed value and caches it for reuse across components.

  • It returns a function that you call, e.g. activeTodos().

  • Automatic Recalculation: Whenever the underlying state.todos changes, calling activeTodos() will return the updated result.

  • Component Re-render: Your component will re-render if you subscribe to the relevant parts of state, e.g. useStore(s => s.todos).

2. Using useNeutrixComputed for Component-Specific Computed Values

Sometimes, you want a one-off or component-specific computed value that you don’t need to store globally or name. In that case, useNeutrixComputed(fn) calculates a derived result on-demand and automatically tracks dependencies.

typescript
import React from 'react';
import { useNeutrixComputed } from 'neutrix';

import { store, useStore } from './store';

function TodoStats() {
  const activeTodos = useNeutrixComputed(state => {
    return state.todos.filter(todo => !todo.completed);
  });

  const total = useStore(s => s.todos.length);

  return (
    <div>
      <p>Total Todos: {total}</p>
      <p>Active Todos: {activeTodos.length}</p>
    </div>
  );
}

Differences vs. store.computed:

  • store.computed(path, fn)

    • Named and cached in the store.
    • Multiple components can call that function (e.g., activeTodos()).
  • useNeutrixComputed(fn)

    • Only lives in the current component.
    • Perfect for ephemeral or single-use derived data.

3. Automatic Dependency Tracking

Neutrix uses a proxy-based system to automatically track dependencies:

typescript
const userStatus = useNeutrixComputed(store => {
  if (!store.user.isLoggedIn) return 'Offline';
  if (store.user.lastActive < Date.now() - 5000) return 'Away';
  return 'Online';
});

No manual dependency arrays—Neutrix knows when store.user.isLoggedIn or store.user.lastActive changes and re-computes accordingly.

Performance Features

1. LRU Caching

Computed values are cached using an LRU (Least Recently Used) cache:

  • Default cache size: 50 entries
  • Automatic cache invalidation when dependencies change
  • Memory-efficient for large applications

2. Dependency Graph

typescript
const fullName = useNeutrixComputed(store => {
  // dependency graph tracks these automatically:
  const first = store.user.firstName;  // Dependency 1
  const last = store.user.lastName;    // Dependency 2
  return `${first} ${last}`;
});

When either user.firstName or user.lastName changes, fullName is re-evaluated.

3. Circular Dependencies

Neutrix automatically detects and prevents circular dependencies:

typescript
const circular = store.computed('circular', state => {
  return circular(); // Error: Circular dependency detected
});

React Integration

1. Using with Hooks

typescript
function UserProfile() {
  const fullName = useNeutrixComputed(store => {
    return `${store.user.firstName} ${store.user.lastName}`;
  });

  const userStatus = useStore(store => store.computed.userStatus());

  return (
    <div>
      <h2>{fullName}</h2>
      <p>Status: {userStatus}</p>
    </div>
  );
}