Skip to content

Best Practices

Store Organization

Structure Your State Thoughtfully

Your store's state structure is crucial for maintainability and performance. Because Neutrix uses path-based updates, a flatter state often performs better and is easier to reason about:

typescript
import { createNeutrixStore } from 'neutrix';

// ❌ Avoid deeply nested state
const store = createNeutrixStore({
  users: {
    data: {
      byId: {
        settings: {
          preferences: {}
        }
      }
    }
  }
});

// ✅ Use flatter state structures
const store2 = createNeutrixStore({
  users: {},
  userSettings: {},
  userPreferences: {}
})

Computed Values

Use Computed Properties for Derived State

Instead of manually recalculating or storing derived data, leverage Neutrix's computed system:

typescript
import { createNeutrixStore } from 'neutrix';

// ❌ Avoid storing derived data
store.set('todoCount', store.get('todos').length);
store.set('activeTodoCount', store.get('todos').filter(t => !t.completed).length);

// ✅ Use computed values
const store = createNeutrixStore({ todos: [] });

store.store.computed('stats.todoCount', (state) => state.todos.length);

store.store.computed('stats.activeTodoCount', (state) =>
  state.todos.filter(t => !t.completed).length
);

Batch Operations

Use Batch Updates for Multiple Changes

When you need to update multiple parts of your state at once, call the batch method:

typescript
// ❌ Avoid multiple individual updates
store.store.set('user.lastLogin', new Date());
store.store.set('user.loginCount', count + 1);
store.store.set('ui.lastAction', 'login');

// ✅ Use batch updates
store.store.batch([
  ['user.lastLogin', new Date()],
  ['user.loginCount', count + 1],
  ['ui.lastAction', 'login']
]);

Benefits:

  • Atomic updates
  • Single notification to subscribers
  • Better performance

State Persistence

Be Selective About What You Persist

When using persistence, carefully choose what needs to be saved:

typescript
import { createNeutrixStore } from 'neutrix';

// ❌ Avoid persisting everything
const store = createNeutrixStore(initialState, {
  persist: true
});

// ✅ Be selective about persistence
const selectiveStore = createNeutrixStore(initialState, {
  persist: (state) => ({
    user: state.user,
    settings: state.settings
  }),
  name: 'app-store'
});

Always validate persisted state to ensure data integrity:

typescript
const validatedStore = createNeutrixStore(
  { count: 0, user: null },
  {
    persist: true,
    validate: (state) => {
      if (!state.user) return 'No user found in state';
      return true;
    }
  }
);

Error Handling

Use Actions for Complex Operations

When dealing with async operations or complex state updates, use actions to handle errors properly:

typescript
// ❌ Raw async operations
async function updateUser(userId, data) {
  store.store.set(`users.${userId}.loading`, true);
  try {
    const result = await api.updateUser(userId, data);
    store.store.set(`users.${userId}`, result);
  } catch (e) {
    store.store.set(`users.${userId}.error`, e.message);
  }
}

// ✅ Use actions for better error handling
const updateUserAction = store.store.action(
  async (store, userId: string, data: any) => {
    store.set(`users.${userId}.loading`, true);
    try {
      const result = await api.updateUser(userId, data);
      store.set(`users.${userId}`, result);
      return result;
    } catch (error) {
      store.set(`users.${userId}.error`, (error as Error).message);
      throw error;
    } finally {
      store.set(`users.${userId}.loading`, false);
    }
  }
);

Middleware Usage

Use Middleware for Cross-Cutting Concerns

neutrix's middleware system is powerful but should be used carefully:

typescript
// ❌ Avoid putting business logic in middleware
const businessMiddleware: Middleware = {
  onSet: (path, value) => {
    if (path === 'user.age') {
      return calculateAgeThings(value) // Business logic doesn't belong here
    }
    return value
  }
}

// ✅ Use middleware for cross-cutting concerns
const loggingMiddleware: Middleware = {
  onSet: (path, value, prevValue) => {
    console.log(`${path} changed:`, prevValue, '→', value)
    return value
  }
}

const validationMiddleware: Middleware = {
  onSet: (path, value) => {
    if (value === undefined) {
      throw new Error(`Cannot set ${path} to undefined`)
    }
    return value
  }
}

Store Connections

Use Store Connections for Complex State Synchronization

When multiple stores need to remain in sync:

typescript
import { connectStore } from 'neutrix';

// ❌ Avoid manual store synchronization
storeA.store.subscribe(() => {
  if (someCondition) {
    storeB.store.set('something', storeA.store.get('something'));
  }
});

// ✅ Use connectStore or connectStores
connectStore({
  source: storeA.store,
  target: storeB.store,
  when: (source) => source.get('something') > 0,
  then: (target) => {
    target.set('something', 123);
  }
});