Store

[ ru ]

Store is an object that holds the state value. Store is getting updates when receives a value that is not equal (!==) to current one and to undefined. Store is Unit. Some stores can be derived.



Methods

map(fn)

Creates a derived store. It will call a provided function with the state, when the original store updates, and will use the result to update the derived store

Formulae

const $second = $first.map(fn);
  • When $first store is updated, call fn with new state and previous state
  • Next update $second store with the result of fn() call and trigger all subscribers

Arguments

  1. fn (Function): Function that receives state and lastState? and returns a new state for the derived store

If the function returns an old state or if it returns undefined, the new store will not be updated.

  1. config (Object): Optional configuration
    • skipVoid: (boolean): Flag to control, how specifically store should handle undefined value (since effector 23.0.0). If set to false - store will use undefined as a value. If set to true (deprecated), store will read undefined as a “skip update” command and will do nothing.

Should be pure

Throws

unit call from pure function is not supported, use operators like sample instead

Since: effector 23.0.0

Happens when events or effects called from pure functions, like mappers:

const someHappened = createEvent<number>();
const $counter = createStore(0);

const $stringified = $counter.map((counter) => {
  someHappened(counter); // THROWS!
  return String(counter);
});

To fix this, use sample:

const someHappened = createEvent<number>();
const $counter = createStore(0);

const $stringified = $counter.map((counter) => {
  return String(counter);
});

sample({
  clock: $counter,
  target: someHappened,
});

Returns

DerivedStore: New derived store

Example

import { createEvent, createStore } from "effector";

const changed = createEvent();

const $title = createStore("").on(changed, (_, newTitle) => newTitle);

const $length = $title.map((title) => title.length);

$length.watch((length) => {
  console.log("new length", length);
});
// => new length 0

changed("hello");
// => new length 5

changed("world");
// no reaction

changed("hello world");
// => new length 11

Try it

Skip Void Example

const $length = $title.map((title) => title.length, {skipVoid: false});

on(trigger, reducer)

Updates state when trigger is triggered by using reducer. For each trigger, the last installed reducer will override previous reducers (useful for dynamic behavior).

Formulae

$store.on(trigger, reducer);
  • When trigger is triggered, call reducer with payload of the trigger and data of $store
  • Next update $store with result of reducer call and trigger all subscribers

Arguments

  1. trigger Event, Effect or another Store
  2. reducer Reducer: Function that receives state and params and returns a new state, should be pure. A store cannot hold an undefined value. If a reducer function returns undefined, the store will not be updated.
    • state: Current state of store
    • params: Parameters passed to the event call

Throws

unit call from pure function is not supported, use operators like sample instead

Since: effector 23.0.0

Happens when events or effects called from pure functions, like reducers:

const someHappened = createEvent();
const increment = createEvent();
const $counter = createStore(0);

$counter.on(increment, (counter) => {
  someHappened(); // THROWS!
  return counter + 1;
});

To fix this use sample:

const someHappened = createEvent();
const increment = createEvent();
const $counter = createStore(0);

$counter.on(increment, (counter) => {
  return counter + 1;
});

sample({
  clock: increment,
  source: $counter,
  target: someHappened,
});

Returns

Store: Current store

Example

import { createEvent, createStore } from "effector";

const $store = createStore(0);
const changed = createEvent();

$store.on(changed, (value, incrementor) => value + incrementor);

$store.watch((value) => {
  console.log("updated", value);
});
// => updated 0

changed(2);
// => updated 2

changed(2);
// => updated 4

Try it

on(triggers[], reducer)

Updates state when any from triggers is triggered by using reducer.

Formulae

$store.on([triggerA, triggerB, ...], reducer)
  • When triggerA or triggerB is triggered, call reducer with payload of the triggerA or triggerB and data of $store
  • Next update $store with result of reducer call and trigger all subscribers
  • Any count of triggers can be passed to triggers

Arguments

  1. triggers array of Event, Effect or Store
  2. reducer Reducer: Function that receives state and params and returns a new state, should be pure. A store cannot hold an undefined value. If a reducer function returns undefined, the store will not be updated.
    • state: Current state of store
    • payload: Value passed to event/effect call, or source if it passed as trigger

Throws

The same as on(trigger, reducer)

Returns

Store: Current store

Example

import { createEvent, createStore } from "effector";

const changedA = createEvent();
const changedB = createEvent();

const $store = createStore(0);

$store.on([changedA, changedB], (value, incrementor) => value + incrementor);

$store.watch((value) => {
  console.log("updated", value);
});

changedA(2);
// => updated 2

changedB(2);
// => updated 4

// You can unsubscribe from any trigger
$store.off(changedA);

Try it

watch(watcher)

Call watcher function each time when store is updated.

Formulae

const unwatch = $store.watch(watcher);
  • On initialize and each $store update, call watcher with the new state of $store
  • When unwatch is called, stop calling watcher

Arguments

  1. watcher (Watcher): Watcher function that receives current store state as the first argument

Returns

Subscription: Unsubscribe function

Example

const add = createEvent();
const $store = createStore(0).on(add, (state, payload) => state + payload);

$store.watch((value) => console.log(`current value: ${value}`));
// => current value: 0
add(4);
// => current value: 4
add(3);
// => current value: 7

watch(trigger, watcher)

Run watcher only when trigger event triggered.

Formulae

const unwatch = $store.watch(trigger, watcher);
  • On each $store update with passed trigger, call watcher with the new state of $store and payload from trigger
  • When unwatch is called, stop calling watcher

Arguments

  1. trigger Event, Effect or Store: Trigger, which leads to call of watcher
  2. watcher (Function): Function that receives current store state as the first argument and payload of trigger as the second argument.

Returns

Subscription: Unsubscribe function

Example

.watch trigger watcher when foo is executed, because foo is explicitly passed to watch.
First argument of watcher is a state value, second is an event value.

import { createEvent, createStore } from "effector";

const foo = createEvent();
const bar = createEvent();

const $store = createStore(0);

$store.watch(foo, (storeValue, eventValue) => {
  console.log(`triggered ${storeValue}, ${eventValue}`);
});

foo(1);
// => triggered 0, 1

bar(2);

foo(3);
// => triggered 0, 3

Try it

reset(...triggers)

Resets store state to the default value.

A state is reset when Event or Effect is called or another Store is changed.

Formulae

$store.reset(...triggers);
  • When any unit from triggers list is triggered, update $store with its default state, from createStore(defaultState)

Arguments

  1. triggers ((Event | Effect | Store)[]): any number of Events, Effects or Stores

Returns

Store: Current store

Example

import { createEvent, createStore } from "effector";

const increment = createEvent();
const reset = createEvent();

const $store = createStore(0)
  .on(increment, (state) => state + 1)
  .reset(reset);

$store.watch((state) => console.log("changed", state));
// changed 0
// watch method calls its function immediately

increment(); // changed 1
increment(); // changed 2
reset(); // changed 0

Try it

reset(triggersArray)

Resets store state to the default value. An overload for arrays of units, which make reset consistent with merge and store.on(triggers[], fn)

A state is reset when Event or Effect is called or another Store is changed.

Formulae

$store.reset([triggerA, triggerB, ...])
  • When any unit from triggersArray list is triggered, update $store with its default state, from createStore(defaultState)

Arguments

  1. triggersArray ((Event | Effect | Store)[]): any number of Events, Effects or Stores

Returns

Store: Current store

Example

import { createEvent, createStore } from "effector";

const increment = createEvent();
const reset = createEvent();

const $store = createStore(0)
  .on(increment, (state) => state + 1)
  .reset([reset]);

$store.watch((state) => console.log("changed", state));
// changed 0
// watch method calls its function immediately

increment(); // changed 1
increment(); // changed 2
reset(); // changed 0

Try it

off(trigger)

$store.off(trigger);
  • Removes reducer for given trigger, which was installed via $store.on or $store.reset
  • If there was no reducer for that trigger, this method will do nothing

Arguments

  1. trigger: Event, Effect or Store

Returns

Store: Current store

Example

import { createEvent, createStore } from "effector";

const changedA = createEvent();
const changedB = createEvent();

const $store = createStore(0);

// If you want to unsubscribe from all triggers simultaneously, better to manually merge
const changed = merge([changedA, changedB]);

$store.on(changed, (state, params) => state + params);

$store.off(changed);

Try it

Properties

updates

Formulae

$store.updates;
  • When $store is changed trigger updates event with the new state

Returns

Event: Event that represents updates of the given store.

Important

Do not manually call this event. It is an event that depends on a store.

Example

Use case: watchers, which will not trigger immediately after creation (unlike store.watch)

import { createStore, is } from "effector";

const $clicksAmount = createStore(0);
is.event($clicksAmount.updates); // => true

$clicksAmount.watch((amount) => {
  console.log("will be triggered with current state, immediately, sync", amount);
});

$clicksAmount.updates.watch((amount) => {
  console.log("will not be triggered unless store value is changed", amount);
});

Try it

reinit

since

Supported since effector 22.4.0

Formulae

$store.reinit; // Event<void>
  • Trigger reinit to set default value as a current value in the $store

Returns

Event: Event that can reinitialize a store with a default value.

Example

Use case: reset counter to default value

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

const $counter = createStore(0);
is.event($counter.reinit); // => true

const increment = createEvent();

sample({
  clock: increment,
  source: $counter,
  fn: (counter) => counter + 1,
  target: $counter,
});

console.log("Initial value: ", $counter.getState()); // => Initial value: 0

increment();
console.log("Incremented value: ", $counter.getState()); // => Incremented value: 1

$counter.reinit();
console.log("Reinitialized value: ", $counter.getState()); // => Reinitialized value: 0

Try it

shortName

Returns

(string): ID or short name of the store

defaultState

Returns

(State): Default state of the store

Example

const $store = createStore("DEFAULT");

console.log($store.defaultState === "DEFAULT");
// => true

Utility methods

getState()

Returns current state of store

You don't need this method!

getState() usage can result in difficult-to-debug imperative code and create a kind of race condition. Prefer declarative sample to pass data from store and attach for effects

Returns

(State): Current state of the store

Example

import { createEvent, createStore } from "effector";

const add = createEvent();

const $number = createStore(0).on(add, (state, data) => state + data);

$number.watch((n) => {
  console.log(n);
});
// => 0

add(2);
// => 2

add(3);
// => 5

Try it



Readonly store

ReadonlyStore has no specific interface in TypeScript, but it has different implementation in the effector kernel.

Some methods like combine, .map, sample, .pending returns Store instance. The store updates by specific rules defined in the method above. That’s why we have different types of stores.

Derived stores are not allowed to be modified from the outside. For example, you shall not add new triggers on the readonly store:

const update = createEvent();
const $a = createStore(1);
const $b = createStore(2);

const $derived = combine({ a: $a, b: $b });
$derived.on(update, (_, value) => ({ a: value, b: value }));
// => .on in readonly store is deprecated, use createStore instead

Readonly store only allows methods that do not modify state. It means that ReadonlyStore cannot be used as target in sample:

const update = createEvent();
const $a = createStore(1);
const $b = createStore("foo");

const $derived = combine({ a: $a, b: $b });

sample({
  clock: update,
  fn: (value) => ({ a: value, b: value }),
  target: $derived,
});
// => sample: readonly unit in "target" is deprecated, use createEvent/createStore instead

These methods are allowed for ReadonlyStore:

  • .map
  • .watch
  • using ReadonlyStore as a source and/or clock in sample and so on
  • using ReadonlyStore in combine sources
deprecation

since 23.0.0 banned methods will throw an exception

These methods are banned for ReadonlyStore:

  • .on
  • .reset
  • using ReadonlyStore as a target in sample, guard and so on

Moreover, ReadonlyStore does not have the following properties:

  • .reinit, because it is not possible to reinitialize read-only store

Any kind of store can be used as a clock or source in methods like sample.

Contributors