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:
- Events: Trigger state changes
- Stores: Hold application state
- 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
-
Keep model.ts pure: Avoid side effects in store updates.
-
Use TypeScript: Define types for all events, stores, and effects.
-
Organize by domain: Group related events, stores, and effects in domain folders.
-
Use view-model.ts: Create hooks that encapsulate Effector logic for React components.
-
Keep UI components simple: UI components should only consume data from hooks and call events.
-
Test model.ts: Write unit tests for your Effector logic.