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
- Performance Guide - Optimizing Mesa applications
- useStore() - React hook for subscribing to state
- Examples - Practical examples of fine-grained updates