react-spa-template/docs/effector-guide.md
2025-04-19 19:24:27 +02:00

4.8 KiB

Effector Guide

This guide provides an overview of how to use Effector in our application, following our Flux-style architecture.

Core Concepts

Effector is a state management library that implements the Flux pattern with three main primitives:

  1. Events: Trigger state changes
  2. Stores: Hold application state
  3. Effects: Handle side effects (async operations)

Basic Usage

Creating Events

Events are functions that can be called to trigger state changes:

import { createEvent } from 'effector';

// Create an event
export const increment = createEvent();
export const addTodo = createEvent<string>();
export const updateUser = createEvent<{ name: string, email: string }>();

// Call the event
increment();
addTodo('Buy milk');
updateUser({ name: 'John', email: 'john@example.com' });

Creating Stores

Stores hold the application state and update in response to events:

import { createStore } from 'effector';
import { increment, addTodo } from './events';

// Create a store with initial state
export const $counter = createStore(0)
  .on(increment, state => state + 1);

export const $todos = createStore<string[]>([])
  .on(addTodo, (state, todo) => [...state, todo]);

Creating Effects

Effects handle asynchronous operations:

import { createEffect } from 'effector';
import { api } from '@shared/lib/api';

// Create an effect
export const fetchUserFx = createEffect(async (userId: string) => {
  const response = await api.getUser(userId);
  return response.data;
});

// Call the effect
fetchUserFx('123');

Handling Effect States

Effects have three states: pending, done, and fail:

import { createStore } from 'effector';
import { fetchUserFx } from './effects';

// Create stores for different effect states
export const $isLoading = createStore(false)
  .on(fetchUserFx.pending, (_, isPending) => isPending);

export const $user = createStore(null)
  .on(fetchUserFx.doneData, (_, user) => user);

export const $error = createStore(null)
  .on(fetchUserFx.failData, (_, error) => error)
  .on(fetchUserFx, () => null); // Reset error when effect is called

Using with React

useStore Hook

The useStore hook connects Effector stores to React components:

import { useStore } from 'effector-react';
import { $counter, increment } from './model';

const Counter = () => {
  const count = useStore($counter);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => increment()}>Increment</button>
    </div>
  );
};

useEvent Hook

The useEvent hook creates a stable callback for events:

import { useStore, useEvent } from 'effector-react';
import { $todos, addTodo } from './model';

const TodoList = () => {
  const todos = useStore($todos);
  const handleAddTodo = useEvent(addTodo);
  
  return (
    <div>
      <button onClick={() => handleAddTodo('New todo')}>Add Todo</button>
      <ul>
        {todos.map((todo, index) => (
          <li key={index}>{todo}</li>
        ))}
      </ul>
    </div>
  );
};

Advanced Patterns

Derived Stores (Computed Values)

Create derived stores using the .map method:

import { createStore } from 'effector';

export const $todos = createStore([
  { id: 1, text: 'Buy milk', completed: false },
  { id: 2, text: 'Clean house', completed: true }
]);

// Derived store for completed todos
export const $completedTodos = $todos.map(
  todos => todos.filter(todo => todo.completed)
);

// Derived store for todo count
export const $todoCount = $todos.map(todos => todos.length);

Combining Stores

Combine multiple stores into one:

import { createStore, combine } from 'effector';

export const $user = createStore({ name: 'John' });
export const $settings = createStore({ theme: 'dark' });

// Combined store
export const $appState = combine({
  user: $user,
  settings: $settings
});

// Or with a custom mapper function
export const $userData = combine(
  $user,
  $settings,
  (user, settings) => ({
    ...user,
    theme: settings.theme
  })
);

Reset Events

Reset stores to their initial state:

import { createEvent, createStore } from 'effector';

export const reset = createEvent();

export const $counter = createStore(0)
  .reset(reset); // Reset to initial state (0)

// Call reset to restore initial state
reset();

Best Practices

  1. Keep model.ts pure: Avoid side effects in store updates.

  2. Use TypeScript: Define types for all events, stores, and effects.

  3. Organize by domain: Group related events, stores, and effects in domain folders.

  4. Use view-model.ts: Create hooks that encapsulate Effector logic for React components.

  5. Keep UI components simple: UI components should only consume data from hooks and call events.

  6. Test model.ts: Write unit tests for your Effector logic.