Fine-Grained Reactivity

Deep dive into Mesa's path-based dependency tracking system.

Mesa's fine-grained reactivity is what sets it apart from other state management solutions. Instead of re-rendering entire components when any part of the state changes, Mesa tracks exactly which properties each component uses and only triggers updates when those specific properties change.

How It Works

Traditional State Management Problem

In traditional React state management, components re-render whenever the state object changes:

// Traditional approach - ALL components re-render
const [state, setState] = useState({
  count: 0,
  user: { name: "John", age: 25 },
  todos: []
});

function Counter() {
  return <div>{state.count}</div>; // Re-renders even when user changes
}

function UserName() {
  return <div>{state.user.name}</div>; // Re-renders even when count changes
}

Mesa's Solution: Path-Based Tracking

Mesa tracks dependencies at the property path level:

const state = proxy({
  count: 0,
  user: { name: "John", age: 25 },
  todos: []
});

function Counter() {
  // Only subscribes to "count" path
  const count = useStore(state, s => s.count);
  return <div>{count}</div>; // Only re-renders when count changes
}

function UserName() {
  // Only subscribes to "user.name" path
  const name = useStore(state, s => s.user.name);
  return <div>{name}</div>; // Only re-renders when user.name changes
}

Dependency Tracking Process

Mesa's dependency tracking works in three phases:

1. Access Tracking Phase

When useStore runs its selector, Mesa tracks which properties are accessed:

const user = useStore(state, s => {
  // Mesa tracks: "user.name", "user.age"
  return {
    name: s.user.name,
    age: s.user.age
  };
});

2. Subscription Phase

Mesa creates subscriptions only for the accessed paths:

// Component subscribes to:
// - "user.name" 
// - "user.age"
// NOT to "count" or "todos"

3. Update Phase

When state changes, only components subscribed to changed paths re-render:

state.count++; // Only components using "count" re-render
state.user.name = "Jane"; // Only components using "user.name" re-render
state.user.profile = {}; // Components using "user" (entire object) re-render

Path Resolution Examples

Simple Properties

const state = proxy({
  count: 0,
  name: "John"
});

// Tracks path: "count"
const count = useStore(state, s => s.count);

// Tracks path: "name"
const name = useStore(state, s => s.name);

Nested Objects

const state = proxy({
  user: {
    profile: {
      name: "John",
      age: 25
    },
    settings: {
      theme: "dark"
    }
  }
});

// Tracks path: "user.profile.name"
const name = useStore(state, s => s.user.profile.name);

// Tracks path: "user.settings.theme"
const theme = useStore(state, s => s.user.settings.theme);

// Tracks path: "user.profile" (entire object)
const profile = useStore(state, s => s.user.profile);

Array Access (Coarse-Grained)

Mesa uses a coarse-grained approach for arrays - when you subscribe to an array, any change to the array triggers re-renders:

const state = proxy({
  items: [1, 2, 3],
  todos: [
    { id: 1, text: "Task 1", done: false },
    { id: 2, text: "Task 2", done: true }
  ]
});

// Tracks entire "items" array - re-renders on any array change
const items = useStore(state, s => s.items);

// Also tracks entire "todos" array - re-renders when any todo changes
const todos = useStore(state, s => s.todos);

// Changing any property of any todo will re-render all subscribers to todos array
state.todos[0].done = true; // All components using todos will re-render

Identity Selector

Mesa supports subscribing to the entire store without a selector:

const state = proxy({
  count: 0,
  user: { name: "John" },
  todos: []
});

// These are equivalent:
const store1 = useStore(state);           // Identity selector (shorthand)
const store2 = useStore(state, s => s);   // Explicit identity selector

// Both subscribe to all top-level properties and re-render when any change

Advanced Tracking Scenarios

Conditional Access

const state = proxy({
  user: { isLoggedIn: false, profile: null }
});

function UserProfile() {
  const data = useStore(state, s => {
    // Tracks: "user.isLoggedIn"
    if (s.user.isLoggedIn) {
      // Also tracks: "user.profile.name" (when logged in)
      return s.user.profile?.name;
    }
    return null;
  });
  
  return <div>{data}</div>;
}

// This component only re-renders when:
// 1. user.isLoggedIn changes
// 2. user.profile.name changes (if logged in)

Computed Dependencies

function TodoStats() {
  const stats = useStore(state, s => {
    // Tracks entire "todos" array
    const total = s.todos.length;
    const completed = s.todos.filter(t => t.done).length;
    
    return {
      total,
      completed,
      remaining: total - completed
    };
  });
  
  return (
    <div>
      <div>Total: {stats.total}</div>
      <div>Completed: {stats.completed}</div>
      <div>Remaining: {stats.remaining}</div>
    </div>
  );
}

// Re-renders when any todo is added, removed, or its 'done' status changes

Dynamic Path Access

function DynamicAccess({ userId }) {
  const userName = useStore(state, s => {
    // Tracks different paths based on userId
    return s.users[userId]?.name;
  });
  
  return <div>{userName}</div>;
}

// Subscription updates when userId changes

Performance Benefits

Render Reduction

// Without Mesa - all components re-render
const [state, setState] = useState({ count: 0, user: { name: "John" } });

// With Mesa - only affected components re-render
const state = proxy({ count: 0, user: { name: "John" } });

state.count++; // Only Counter components re-render
state.user.name = "Jane"; // Only UserName components re-render

Memory Efficiency

Mesa's path-based system is memory efficient:

  • Only stores subscriptions for accessed paths
  • Automatically cleans up unused subscriptions
  • No unnecessary re-computations

Scalability

As your app grows, Mesa's benefits compound:

const state = proxy({
  ui: { /* 20 properties */ },
  data: { /* 50 properties */ },
  cache: { /* 100 properties */ }
});

// Component only subscribes to what it uses
function SmallComponent() {
  const loading = useStore(state, s => s.ui.loading);
  return loading ? <Spinner /> : <Content />;
}

Debugging Dependency Tracking

Tracking Current Dependencies

You can debug which paths a component tracks:

function DebugComponent() {
  const data = useStore(state, s => {
    console.log('Selector running'); // Logs when dependencies change
    return {
      count: s.count,
      name: s.user.name
    };
  });
  
  console.log('Component rendering'); // Logs when component re-renders
  return <div>{data.count} - {data.name}</div>;
}

Understanding Re-render Causes

// This will re-render when ANY property of user changes
const user = useStore(state, s => s.user);

// This will only re-render when user.name changes
const name = useStore(state, s => s.user.name);

// This will re-render when user.name OR user.age changes
const info = useStore(state, s => ({
  name: s.user.name,
  age: s.user.age
}));

Best Practices for Fine-Grained Updates

✅ Select Specific Properties

// Good - specific selection for objects
const name = useStore(state, s => s.user.name);
const age = useStore(state, s => s.user.age);

// Consider - broader selection when you need multiple related properties
const user = useStore(state, s => s.user); // OK if you need multiple user properties

// Good - identity selector for small, simple state
const state = useStore(simpleStore); // OK for simple state objects

// Avoid - identity selector for large, complex state
const state = useStore(largeComplexStore); // Too broad, causes unnecessary re-renders

✅ Use Multiple Subscriptions

// Good - separate concerns
function UserCard() {
  const name = useStore(state, s => s.user.name);
  const avatar = useStore(state, s => s.user.avatar);
  const isOnline = useStore(state, s => s.user.isOnline);
  
  // Each property can update independently
  return <div>{name} - {isOnline ? "Online" : "Offline"}</div>;
}

✅ Optimize Computed Values

// Good - memoize expensive computations
const expensiveValue = useMemo(() => {
  return useStore(state, s => 
    s.largeList.filter(item => item.isActive).length
  );
}, []);

// Or use a separate computed state
const computedState = proxy({
  get activeCount() {
    return state.largeList.filter(item => item.isActive).length;
  }
});

Common Patterns

Parent-Child Communication

function TodoApp() {
  // Parent only re-renders when todos array structure changes
  const todos = useStore(state, s => s.todos);
  
  return (
    <div>
      {todos.map(todo => (
        <TodoItem key={todo.id} todoId={todo.id} />
      ))}
    </div>
  );
}

function TodoItem({ todoId }) {
  // Child only re-renders when this specific todo changes
  const todo = useStore(state, s => 
    s.todos.find(t => t.id === todoId)
  );
  
  return <div>{todo?.text}</div>;
}

Conditional Rendering

function ConditionalContent() {
  const isLoading = useStore(state, s => s.ui.isLoading);
  const error = useStore(state, s => s.ui.error);
  const data = useStore(state, s => s.data);
  
  if (isLoading) return <Loading />; // Only re-renders when isLoading changes
  if (error) return <Error />; // Only re-renders when error changes  
  return <Content data={data} />; // Only re-renders when data changes
}

Mesa's fine-grained reactivity system ensures your React applications remain performant as they scale, eliminating unnecessary re-renders and providing predictable update behavior.

See Also