Skip to content

Performance

Optimize BlaC applications for best performance.

Key Principles

  1. Access only what you render - Proxy tracking records property access
  2. Use getters for computed values - Cached per render cycle
  3. Use useBlocActions for action-only components - No subscription overhead
  4. Split large components - Smaller components re-render independently

Optimal Property Access

tsx
// ✅ OPTIMAL: Access only rendered properties
function UserCard() {
  const [user] = useBloc(UserCubit);
  return (
    <div>
      <img src={user.avatar} />
      <h2>{user.name}</h2>
    </div>
  );
  // Only tracks 'avatar' and 'name'
  // Changes to email, bio won't trigger re-render
}

// ❌ AVOID: Destructuring tracks all destructured
function UserCard() {
  const [user] = useBloc(UserCubit);
  const { name, email, avatar, bio } = user;
  return <h2>{name}</h2>;
  // Tracks ALL four properties even though only name is rendered
}

// ❌ AVOID: Spreading defeats tracking
function UserCard() {
  const [user] = useBloc(UserCubit);
  return <Profile {...user} />;
  // Tracks everything!
}

Nested Access

tsx
// ✅ Direct nested access works
function UserAvatar() {
  const [user] = useBloc(UserCubit);
  return <img src={user.profile.avatar} />;
  // Tracks 'profile.avatar'
}

// ✅ Conditional access
function UserStatus() {
  const [user] = useBloc(UserCubit);
  return user.isOnline ? <span>Online</span> : null;
  // Tracks 'isOnline'
}

// ✅ Array index access
function FirstTodo() {
  const [state] = useBloc(TodoCubit);
  return <div>{state.todos[0]?.text}</div>;
  // Tracks todos[0]
}

Computed Values with Getters

tsx
class TodoCubit extends Cubit<TodoState> {
  // Getter computed once per render (cached)
  get visibleTodos() {
    return this.state.filter === 'active'
      ? this.state.todos.filter((t) => !t.done)
      : this.state.todos;
  }

  get activeCount() {
    return this.state.todos.filter((t) => !t.done).length;
  }
}

function TodoList() {
  const [, cubit] = useBloc(TodoCubit);

  // Multiple accesses = one computation
  return (
    <div>
      <h2>{cubit.visibleTodos.length} visible</h2>
      <ul>
        {cubit.visibleTodos.map((todo) => (
          <li key={todo.id}>{todo.text}</li>
        ))}
      </ul>
    </div>
  );
}

Action-Only Components

tsx
// ✅ OPTIMAL: No state subscription
function TodoActions() {
  const cubit = useBlocActions(TodoCubit);

  return (
    <div>
      <button onClick={() => cubit.addTodo('New')}>Add</button>
      <button onClick={cubit.clearCompleted}>Clear</button>
    </div>
  );
  // Never re-renders from TodoCubit state changes
}

// ❌ WASTEFUL: Subscribes but never uses state
function TodoActions() {
  const [_, cubit] = useBloc(TodoCubit);
  return <button onClick={cubit.addTodo}>Add</button>;
}

Component Splitting

tsx
// ✅ OPTIMAL: Split into focused components
function TodoApp() {
  return (
    <>
      <TodoCount />    {/* Re-renders on count change */}
      <TodoList />     {/* Re-renders on todos change */}
      <TodoFilter />   {/* Re-renders on filter change */}
      <TodoActions />  {/* Never re-renders */}
    </>
  );
}

function TodoCount() {
  const [, cubit] = useBloc(TodoCubit);
  return <span>Active: {cubit.activeCount}</span>;
}

function TodoList() {
  const [, cubit] = useBloc(TodoCubit);
  return <ul>{cubit.visibleTodos.map(...)}</ul>;
}

function TodoFilter() {
  const [state, cubit] = useBloc(TodoCubit);
  return (
    <select value={state.filter} onChange={e => cubit.setFilter(e.target.value)}>
      <option value="all">All</option>
      <option value="active">Active</option>
    </select>
  );
}

function TodoActions() {
  const cubit = useBlocActions(TodoCubit);
  return <button onClick={() => cubit.addTodo('New')}>Add</button>;
}

// ❌ AVOID: One big component
function TodoApp() {
  const [state, cubit] = useBloc(TodoCubit);
  return (
    <div>
      <span>{cubit.activeCount}</span>
      <ul>{cubit.visibleTodos.map(...)}</ul>
      <select value={state.filter} onChange={...} />
      <button onClick={cubit.addTodo}>Add</button>
    </div>
  );
  // Entire component re-renders on ANY state change
}

Memory-Efficient Patterns

tsx
import { borrow, forEach, release, getAll } from '@blac/core';

// ✅ Use borrow() in bloc-to-bloc (no ref count)
class UserCubit extends Cubit<UserState> {
  loadProfile = () => {
    const analytics = borrow(AnalyticsCubit); // Borrow
    analytics.trackEvent('profile_loaded');
    // No cleanup needed
  };
}

// ✅ Use forEach() for large instance sets
function cleanupStaleSessions() {
  // Memory efficient, safe to dispose during iteration
  forEach(UserSessionCubit, (session) => {
    if (session.state.isStale) {
      release(UserSessionCubit, session.instanceId);
    }
  });
}

// ❌ Avoid getAll() for large sets
const sessions = getAll(UserSessionCubit); // Creates array copy

Manual Dependencies

When you know exactly what to track:

tsx
// Override auto-tracking for specific cases
const [state] = useBloc(UserCubit, {
  dependencies: (state) => [state.name, state.email],
});

// Ignore internal tracking data
const [state] = useBloc(AnalyticsCubit, {
  dependencies: (state) => [state.displayMetrics],
});

// Custom re-render logic for complex cases
const [state] = useBloc(AnalyticsCubit, {
  dependencies: (state) => [state.dataPoints.length],
});

// use a getter in the dependencies
const [state] = useBloc(AnalyticsCubit, {
  dependencies: (state, bloc) => [bloc.computedValue],
});

Performance Summary

PatternRe-renders WhenUse When
Auto-tracking (default)Accessed properties changeMost cases
useBlocActionsNeverAction-only components
Manual dependenciesDependency array changesKnown patterns
autoTrack: falseAny state changeSimple state, debugging
GettersComputed value changesDerived state

Common Mistakes

MistakeImpactFix
Destructuring stateTracks all destructuredAccess directly
Spreading props {...state}Tracks everythingPass specific props
acquire() in methodsMemory leaksUse borrow()
Not using useBlocActionsUnnecessary subscriptionsSplit components
Single large componentOver-renderingSplit into smaller
Array iteration for single itemTracks whole arrayUse index access

See Also

Released under the MIT License.