Bu sahifa hali tarjima qilinmagan

Tarjima qoshish uchun havola boyicha o'tib Pull Request oching (havolaga o'tish).

Standart til uchun tarkibni ko'rsatadi.

State Management

State in effector is managed through stores - special objects that hold values and update them when receiving events. Stores are created using the createStore function.

Data immutability

Store data in effector is immutable, which means you should not mutate arrays or objects directly, but create new instances when updating them.

// update array
$users.on(userAdded, (users, newUser) => [...users, newUser]);

//update object
$user.on(nameChanged, (user, newName) => ({
  ...user,
  name: newName,
}));

Store Creation

You can create new store via createStore:

import { createStore } from "effector";

// Create store with initial value
const $counter = createStore(0);
// with explicit typing
const $user = createStore<{ name: "Bob"; age: 25 } | null>(null);
const $posts = createStore<Post[]>([]);
Store naming

In effector it’s conventional to use the $ prefix for stores. This helps distinguish them from other entities and improves code readability.

Reading Values

There are several ways to get the current value of a store:

  1. Using framework integration hooks useUnit (📘 React, 📗 Vue, 📘 Solid):
import { useUnit } from 'effector-react'
import { $counter } from './model.js'

const Counter = () => {
  const counter = useUnit($counter)

  return <div>{counter}</div>
}
  1. Subscribe to changes via watch - only for debug or integration needs
$counter.watch((counter) => {
  console.log("Counter changed:", counter);
});
  1. getState() method - only for integration needs
console.log($counter.getState()); // 0

Store Updates

In effector, state updates are done via events. You can change the state by subscribing to an event via .on or by using the sample method.

Optimizing updates

Store state is updated when it receives a value that is not equal (!==) to the current value, and also not equal to undefined.

Updating via Events

The simplest and correct way to update a store is to bind it to an event:

import { createStore, createEvent } from "effector";

const incremented = createEvent();
const decremented = createEvent();
const resetCounter = createEvent();

const $counter = createStore(0)
  // Increase value by 1 each time the event is called
  .on(incremented, (counterValue) => counterValue + 1)
  // Decrease value by 1 each time the event is called
  .on(decremented, (counterValue) => counterValue - 1)
  // Reset value to 0
  .reset(resetCounter);

$counter.watch((counterValue) => console.log(counterValue));

// Usage
incremented();
incremented();
decremented();

resetCounter();

// Console output
// 0 - output on initialization
// 1
// 2
// 1
// 0 - reset
What are events?

If you are not familiar with createEvent and events, you will learn how to work with them on next page.

Updating with Event parameters

You can update a store using event parameters by passing data to the event like a regular function and using it in the handler:

import { createStore, createEvent } from "effector";

const userUpdated = createEvent<{ name: string }>();

const $user = createStore({ name: "Bob" });

$user.on(userUpdated, (user, changedUser) => ({
  ...user,
  ...changedUser,
}));

userUpdated({ name: "Alice" });

Complex Update Logic

Using the on method, we can update store state for simple cases when an event occurs, either by passing data from the event or updating based on the previous value.

However, this doesn’t always cover all needs. For more complex state update logic, we can use the sample method, which helps us when:

  • We need to control store updates using an event
  • We need to update a store based on values from other stores
  • We need data transformation before updating the store with access to current values of other stores

For example:

import { createEvent, createStore, sample } from "effector";

const updateItems = createEvent();

const $items = createStore([1, 2, 3]);
const $filteredItems = createStore([]);
const $filter = createStore("even");

// sample automatically provides access to current values
// of all connected stores at the moment the event triggers
sample({
  clock: updateItems,
  source: { items: $items, filter: $filter },
  fn: ({ items, filter }) => {
    if (filter === "even") {
      return items.filter((n) => n % 2 === 0);
    }

    return items.filter((n) => n % 2 === 1);
  },
  target: $filteredItems,
});
What is sample?

To learn more about what sample is, how to use this method, and its detailed description, you can read about it here.

Advantages of using sample for state updates:

  1. Access to current values of all stores
  2. Atomic updates of multiple stores
  3. Control over update timing through clock
  4. Ability to filter updates using filter
  5. Convenient data transformation through the fn function

Store Creation via restore method

If your store work involves replacing the old state with a new one when an event is called, you can use the restore method:

import { restore, createEvent } from "effector";

const nameChanged = createEvent<string>();

const $counter = restore(nameChanged, "");

The code above is equivalent to the code below:

import { createStore, createEvent } from "effector";

const nameChanged = createEvent<string>();

const $counter = createStore("").on(nameChanged, (_, newName) => newName);

You can also use restore method with an effect. In this case, the store will receive data from the effect’s doneData event, and the default store value should match the return value type:

What are effects?

If you are not familiar with createEffect and effects, you will learn how to work with them on this page.

import { restore, createEffect } from "effector";

// omit type realization
const createUserFx = createEffect<string, User>((id) => {
  // effect logic

  return {
    id: 4,
    name: "Bob",
    age: 18,
  };
});

const $newUser = restore(createEffect, {
  id: 0,
  name: "",
  age: -1,
});

createUserFx();

// After successful completion of the effect
// $newUser will be:
// {
// 	 id: 4,
// 	 name: "Bob",
// 	 age: 18,
// }

Multiple Store Updates

A store isn’t limited to a single event subscription - you can subscribe to as many events as you need, and different stores can subscribe to the same event:

const categoryChanged = createEvent<string>();
const searchQueryChanged = createEvent<string>();
const filtersReset = createEvent();

const $lastUsedFilter = createStore<string | null>(null);
const $filters = createStore({
  category: "all",
  searchQuery: "",
});

// subscribe two different stores to the same event
$lastUsedFilter.on(categoryChanged, (_, category) => category);
$filters.on(categoryChanged, (filters, category) => ({
  ...filters,
  category,
}));

$filters.on(searchQueryChanged, (filters, searchQuery) => ({
  ...filters,
  searchQuery,
}));

$filters.reset(filtersReset);

In this example, we subscribe the $filters store to multiple events, and multiple stores to the same event categoryChanged.

Simplified Updates with createApi

When you need to create multiple handlers for one store, instead of creating separate events and subscribing to them, you can use createApi. This function creates a set of events for updating the store in one place.
The following code examples are equivalent:

import { createStore, createApi } from "effector";

const $counter = createStore(0);

const { increment, decrement, reset } = createApi($counter, {
  increment: (state) => state + 1,
  decrement: (state) => state - 1,
  reset: () => 0,
});

// usage
increment(); // 1
reset(); // 0

Derived Stores

Often you need to create a store whose value depends on other stores. For this, the map method is used:

import { createStore, combine } from "effector";

const $currentUser = createStore({
  id: 1,
  name: "Winnie Pooh",
});
const $users = createStore<User[]>([]);

// Filtered list
const $activeUsers = $users.map((users) => users.filter((user) => user.active));

// Computed value
const $totalUsersCount = $users.map((users) => users.length);
const $activeUsersCount = $activeUsers.map((users) => users.length);

// Combining multiple stores
const $friendsList = combine($users, $currentUser, (users, currentUser) =>
  users.filter((user) => user.friendIds.includes(currentUser.id)),
);

We also used the combine method here, which allows us to combine values from multiple stores into one.
You can also combine stores into an object:

import { combine } from "effector";

const $form = combine({
  name: $name,
  age: $age,
  city: $city,
});

// or with additional transformation
const $formValidation = combine($name, $age, (name, age) => ({
  isValid: name.length > 0 && age >= 18,
  errors: {
    name: name.length === 0 ? "Required" : null,
    age: age < 18 ? "Must be 18+" : null,
  },
}));
Important note

Derived stores update automatically when source stores change. You don’t need to manually synchronize their values.

Resetting State

You can reset store to default state via reset method:

const formSubmitted = createEvent();
const formReset = createEvent();

const $form = createStore({ email: "", password: "" })
  // Clear form on submit and on explicit reset too
  .reset(formSubmitted, formReset)
  // or
  .reset([formSubmitted, formReset]);

undefined Values

By default, effector skips updates with undefined value. This is done so that you don’t have to return anything from reducers if store update is not required:

const $store = createStore(0).on(event, (_, newValue) => {
  if (newValue % 2 === 0) {
    return;
  }

  return newValue;
});
Attention!

This behavior will be disabled in the future! Practice has shown that it would be better to simply return the previous store value.

If you need to use undefined as a valid value, you need to explicitly specify it using skipVoid: false when creating the store:

import { createStore, createEvent } from "effector";

const setVoidValue = createEvent<number>();

// ❌ undefined will be skipped
const $store = createStore(13).on(setVoidValue, (_, voidValue) => voidValue);

// ✅ undefined allowed as values
const $store = createStore(13, {
  skipVoid: false,
}).on(setVoidValue, (_, voidValue) => voidValue);

setVoidValue(null);
null instead of undefined

You can use null instead of undefined for missing values.

Full API reference for store

Tarjima jamiyat tomonidan qollanilyapti

Ingliz tilidagi hujjatlar eng dolzarb hisoblanadi, chunki u effector guruhi tomonidan yozilgan va yangilanadi. Hujjatlarni boshqa tillarga tarjima qilish jamiyat tomonidan kuch va istaklar mavjud bo'lganda amalga oshiriladi.

Esda tutingki, tarjima qilingan maqolalar yangilanmasligi mumkin, shuning uchun eng aniq va dolzarb ma'lumot uchun hujjatlarning asl inglizcha versiyasidan foydalanishni tavsiya etamiz.

Hammualliflar