import { type Event, type EventCallable } from "effector";
The Event in effector represents a user action, a step in the application process, a command to execute, or an intention to make modifications, among other things. This unit is designed to be a carrier of information/intention/state within the application, not the holder of a state.
EventCallable<T>
Construction
There are many ways to create an event:
- The most common
createEvent
- Using Domain
createEvent
- Via Event’s methods and it’s supertype EventCallable’s methods
- Some Effect’s methods return new events and readonly events
- Operators such as:
createApi
Declaring types
Event carries some data and in a TypeScript ecosystem each data should have a defined type. When an event is explicitly created by createEvent
, type of the argument must be provided as a Generic type argument:
import { createEvent } from "effector";
interface ItemAdded {
id: string;
title: string;
}
const itemAdded = createEvent<ItemAdded>();
In most cases, there is no reason to use void
with another type (). Use Event<void | number>
void
only to declare the Event or EventCallable without the argument at all. That’s why it is possible to send data from an event with an argument into an event without an argument.
sample({
clock: withData, // Event<number>
target: withoutData, // Event<void>
});
We strongly recommend using null
for empty values when intended:
import { createEvent } from "effector";
const maybeDataReceived = createEvent<Data | null>();
// maybeDataReceived: EventCallable<Data | null>
Read more in the explanation section.
Call as function event(argument)
Initiates an event with the provided argument, which in turn activates any registered subscribers.
Read more in the explanation section.
Formulae
const event: EventCallable<T>;
event(argument: T): T;
event
called as a function always returns itsargument
as is- all subscribers of event receives the
argument
passed into - when
T
isvoid
,event
can be called without arguments T
by default isvoid
, so generic type argument can be omitted
In Effector, any event supports only a single argument. It is not possible to call an event with two or more arguments, as in someEvent(first, second)
.
All arguments beyond the first will be ignored. The core team has implemented this rule for specific reasons related to the design and functionality.
Arguments
argument
is a value ofT
. It’s optional if the event is defined asEventCallable<void>
.
Throws
call of readonly event is not supported, use createEvent instead
When user tried to call Event
. In the most cases it happens when you tried to call derived event:
const numberReceived = createEvent<number>(); // EventCallable<number>
const stringifiedReceived = numberReceived.map((number) => String(number)); // Event<string>
stringifiedReceived("123"); // THROWS!
The same for all methods returning Event
.
To fix it create separate event via createEvent
, and connect them by sample
:
const numberReceived = createEvent<number>();
const stringifiedReceived = createEvent<string>();
sample({
clock: numberReceived,
fn: (number) => String(number),
target: stringifiedReceived,
});
stringifiedReceived("123"); // OK
unit call from pure function is not supported, use operators like sample instead
Happens when events or effects called from pure functions, like mappers:
const someHappened = createEvent<number>();
const another = createEvent();
const derived = someHappened.map((number) => {
another(); // THROWS!
return String(number);
});
To fix this, use sample
:
const someHappened = createEvent<number>();
const another = createEvent();
const derived = createEvent<string>();
sample({
clock: someHappened,
target: another,
});
// The same as .map(), but using `target`
sample({
clock: someHappened,
fn: (number) => String(number),
target: derived,
});
Returns
T
: Represents the same value that is passed into the event
.
Types
import { createEvent, Event } from "effector";
const someHappened = createEvent<number>();
// someHappened: EventCallable<number>
someHappened(1);
const anotherHappened = createEvent();
// anotherHappened: EventCallable<void>
anotherHappened();
An event can be specified with a single generic type argument. By default, this argument is set to void, indicating that the event does not accept any parameters.
Methods
Since the createEvent
factory creates EventCallable
for you, its methods will be described first, even though it is a extension of the Event
type.
All the methods and properties from Event are also available on EventCallable
instance.
You can think of the EventCallable and Event as type and its super type:
EventCallable<T> extends Event<T>
.prepend(fn)
Creates a new EventCallable
, that should be called, upon trigger it sends transformed data into the original event.
Works kind of like reverse .map
. In case of .prepend
data transforms before the original event occurs and in the case of .map
, data transforms after original event occurred.
If the original event belongs to some domain, then a new event will belong to it as well.
Formulae
const first: EventCallable<T>;
const second: EventCallable<T> = first.prepend(fn);
- When
second
event is triggered - Call
fn
with argument from thesecond
event - Trigger
first
event with the result offn()
Arguments
fn
(Function): A function that receivesargument
, and should be pure.
Throws
unit call from pure function is not supported, use operators like sample instead
Happens when events or effects called from pure functions, like mappers:
const someHappened = createEvent<string>();
const another = createEvent<number>();
const reversed = someHappened.prepend((input: number) => {
another(input); // THROWS!
return String(input);
});
To fix this, use sample
:
const someHappened = createEvent<string>();
const another = createEvent<number>();
const reversed = createEvent<number>();
// The same as .prepend(), but using `sample`
sample({
clock: reversed,
fn: (input) => String(input),
target: someHappened,
});
sample({
clock: reversed,
target: another,
});
Returns
EventCallable<T>
: New event.
Types
There TypeScript requires explicitly setting type of the argument of fn
function:
import { createEvent } from "effector";
const original = createEvent<{ input: string }>();
const prepended = original.prepend((input: string) => ({ input }));
// ^^^^^^ here
Type of the original
event argument and the resulting type of the fn
must be the same.
Examples
Basic
import { createEvent } from "effector";
const userPropertyChanged = createEvent();
userPropertyChanged.watch(({ field, value }) => {
console.log(`User property "${field}" changed to ${value}`);
});
const changeName = userPropertyChanged.prepend((name) => ({
field: "name",
value: name,
}));
const changeRole = userPropertyChanged.prepend((role) => ({
field: "role",
value: role.toUpperCase(),
}));
changeName("john");
// => User property "name" changed to john
changeRole("admin");
// => User property "role" changed to ADMIN
changeName("alice");
// => User property "name" changed to alice
Meaningful example
You can think of this method like a wrapper function. Let’s assume we have function with not ideal API, but we want to call it frequently:
import { sendAnalytics } from "./analytics";
export function reportClick(item: string) {
const argument = { type: "click", container: { items: [arg] } };
return sendAnalytics(argument);
}
This is exactly how .prepend()
works:
import { sendAnalytics } from "./analytics";
export const reportClick = sendAnalytics.prepend((item: string) => {
return { type: "click", container: { items: [arg] } };
});
reportClick("example");
// reportClick triggered "example"
// sendAnalytics triggered { type: "click", container: { items: ["example"] } }
Check all other methods on Event.
Event<T>
A Event is a super type of EventCallable
with different approach. Firstly, invoking a Event is not allowed, and it cannot be used as a target
in the sample
operator, and so on.
The primary purpose of a Event is to be triggered by internal code withing the effector library or ecosystem. For instance, the .map()
method returns a Event, which is subsequently called by the .map()
method itself.
There is no need for user code to directly invoke such an Event.
If you find yourself needing to call a Event, it may be necessary to reevaluate and restructure your application’s logic.
All the functionalities provided by an Event are also supported in an EventCallable.
Construction
There is no way to manually create Event, but some methods and operators returns derived events, they are return Event<T>
type:
- Event’s methods like:
.map(fn)
,.filter({fn})
, and so on - Store’s property: ‘.updates’
- Effect’s methods and properties
- operators like:
sample
,merge
Throws
- Errors related to incorrect usage: More details in specific method sections.
Declaring types
It becomes necessary in cases where a factory or library requires an event to subscribe to its updates, ensuring proper integration and interaction with the provided functionality:
const event: Event<T>;
Methods
.map(fn)
Creates a new derived Event, which will be called after the original event is called, using the result of the fn function as its argument. This special function enables you to break down and manage data flow, as well as extract or transform data within your business logic model.
Formulae
const first: Event<T> | EventCallable<T>;
const second: Event<F> = first.map(fn);
- When
first
is triggered, pass payload fromfirst
tofn
. - Trigger
second
with the result of thefn()
call as payload. - The function
fn
is invoked each time thefirst
event is triggered. - Also, the
second
event triggered each time thefirst
is triggered.
Arguments
fn
(Function): A function that receivesargument
, and should be pure.
Throws
unit call from pure function is not supported, use operators like sample instead
Happens when events or effects called from pure functions, like mappers:
const someHappened = createEvent<number>();
const another = createEvent();
const derived = someHappened.map((number) => {
another(); // THROWS!
return String(number);
});
To fix this, use sample
:
const someHappened = createEvent<number>();
const another = createEvent();
const derived = createEvent<string>();
sample({
clock: someHappened,
target: another,
});
// The same as .map(), but using `target`
sample({
clock: someHappened,
fn: (number) => String(number),
target: derived,
});
Returns
Event<T>
: The new event.
Types
The resulting type of the fn
function will be utilized to define the type of the derived event.
import { createEvent } from "effector";
const first = createEvent<number>();
// first: Event<number>
const second = first.map((count) => count.toString());
// second: Event<string>
The first
event can be represented as either Event<T>
or Event<T>
.
The second
event will always be represented as Event<T>
.
Examples
import { createEvent } from "effector";
const userUpdated = createEvent();
// you may decompose dataflow with .map() method
const userNameUpdated = userUpdated.map(({ user }) => name);
// either way you can transform data
const userRoleUpdated = userUpdated.map((user) => user.role.toUpperCase());
userNameUpdated.watch((name) => console.log(`User's name is [${name}] now`));
userRoleUpdated.watch((role) => console.log(`User's role is [${role}] now`));
userUpdated({ name: "john", role: "admin" });
// => User's name is [john] now
// => User's role is [ADMIN] now
.filter({ fn })
This method generates a new derived Event that will be invoked after the original event, but only if the fn
function returns true
. This special function enables you to break down data flow into a branches and subscribe on them within the business logic model.
sample operator with filter
argument is the preferred filtering method.
Formulae
const first: Event<T> | EventCallable<T>;
const second: Event<T> = first.filter({ fn });
- When
first
is triggered, pass payload fromfirst
tofn
. - The
second
event will be triggered only iffn
returnstrue
, with the argument fromfirst
event. - The function
fn
is invoked each time thefirst
event is triggered. - Also, the
second
event triggered each time thefirst
is triggered, and thefn
returnedtrue
.
Arguments
fn
(Function): A function that receivesargument
, and should be pure.
Here, due to legacy restrictions fn
is required to use object form because event.filter(fn)
was an alias for Event filterMap.
Use it always like this .filter({ fn })
.
Throws
unit call from pure function is not supported, use operators like sample instead
Happens when events or effects called from pure functions, like guards:
const countReceived = createEvent<number>();
const eachReceived = createEvent<number>();
const receivedEven = someHappened.filter({
fn(count) {
eachReceived(count); // THROWS!
return count % 2 === 0;
},
});
To fix this, use sample
to call eachReceived
:
const countReceived = createEvent<number>();
const eachReceived = createEvent<number>();
const receivedEven = someHappened.filter({
fn(count) {
return count % 2 === 0;
},
});
sample({
clock: someHappened,
target: eachReceived,
});
Returns
Event<T>
: The new event
Types
Method .filter()
always returns Event. Also this event will have the same type as the original type:
import { createEvent } from "effector";
const numberReceived = createEvent<number>();
// numberReceived: Event<number>
const evenReceived = numberReceived.filter({
fn: (number) => number % 2 === 0,
});
// evenReceived: Event<number>
evenReceived.watch(console.info);
numberReceived(5); // nothing
numberReceived(2); // => 2
Examples
import { createEvent, createStore } from "effector";
const numbers = createEvent();
const positiveNumbers = numbers.filter({
fn: ({ x }) => x > 0,
});
const $lastPositive = createStore(0).on(positiveNumbers, (n, { x }) => x);
$lastPositive.watch((x) => {
console.log("last positive:", x);
});
// => last positive: 0
numbers({ x: 0 });
// no reaction
numbers({ x: -10 });
// no reaction
numbers({ x: 10 });
// => last positive: 10
Meaningful example
Let’s assume a standard situation when you want to buy sneakers in the shop, but there is no size. You subscribe to the particular size of the sneakers’ model, and in addition, you want to receive a notification if they have it, and ignore any other notification. Therefore, filtering can be helpful for that. Event filtering works in the same way. If filter
returns true
, the event will be called.
const sneackersReceived = createEvent<Sneakers>();
const uniqueSizeReceived = sneackersReceived.filter({
fn: (sneackers) => sneackers.size === 48,
});
.filterMap(fn)
This methods generates a new derived Event that may be invoked after the original event, but with the transformed argument. This special method enabled you to simultaneously transform data and filter out trigger of the event.
This method looks like the .filter()
and .map()
merged in the one. That’s it. The reason for creating was an impossibility for event filtering.
This method is mostly useful with JavaScript APIs whose returns undefined
sometimes.
Formulae
const first: Event<T> | EventCallable<T>;
const second: Event<F> = first.filterMap(fn);
- When
first
is triggered, callfn
with payload fromfirst
- If
fn()
returnedundefined
do not triggersecond
- If
fn()
returned some data, triggersecond
with data fromfn()
Arguments
fn
(Function): A function that receivesargument
, should be pure.
The fn
function should return some data. When undefined
is returned, the update of derived event will be skipped.
Throws
unit call from pure function is not supported, use operators like sample instead
Happens when events or effects called from pure functions, like mappers:
const countReceived = createEvent<number>();
const eachReceived = createEvent<number>();
const receivedEven = someHappened.filterMap((count) => {
eachReceived(count); // THROWS!
return count % 2 === 0 ? Math.abs(count) : undefined;
});
To fix this, use sample
to call eachReceived
:
const countReceived = createEvent<number>();
const eachReceived = createEvent<number>();
const receivedEven = someHappened.filterMap((count) => {
return count % 2 === 0 ? Math.abs(count) : undefined;
});
sample({
clock: someHappened,
target: eachReceived,
});
Returns
Event<T>
: The new event
Types
The type for the derived event is automatically inferred from the fn
declaration. No need to explicitly set type for variable or generic type argument:
import { createEvent } from "effector";
const first = createEvent<number>();
// first: Event<number>
const second = first.filterMap((count) => {
if (count === 0) return;
return count.toString();
});
// second: Event<string>
The first
event can be represented as either Event<T>
or EventCallable<T>
.
The second
event will always be represented as Event<T>
.
Examples
import { createEvent } from "effector";
const listReceived = createEvent<string[]>();
// Array.prototype.find() returns `undefined` when no item is found
const effectorFound = listReceived.filterMap((list) => list.find((name) => name === "effector"));
effectorFound.watch((name) => console.info("found", name));
listReceived(["redux", "effector", "mobx"]); // => found effector
listReceived(["redux", "mobx"]);
Meaningful example
Consider a scenario where you walk into a grocery store with a specific task: you need to purchase 10 apples, but only if they’re red. If they’re not red, you’re out of luck. Let’s consider by steps:
- Take one apple;
- Have a look, is it red(put in a pack) or not(take another).
And you repeat this until you complete the task. Now think about it in the effector terms, and we consider the positive case:
- Take an apple – event;
- Have a look, red or no – filter;
- You keep it – map;
- Put in pack – event.
- Pack – store
.watch(watcher)
This method enables you to call callback on each event trigger with the argument of the event.
The watch
method neither handles nor reports exceptions, manages the completion of asynchronous operations, nor addresses data race issues.
Its primary intended use is for short-term debugging and logging purposes.
Read more in the explanation section.
Formulae
const event: Event<T> | EventCallable<T>;
const unwatch: () => void = event.watch(fn);
- The
fn
will be called on eachevent
trigger, passed argument of theevent
to thefn
. - When
unwatch
is called, stop callingfn
on eachevent
trigger.
Arguments
watcher
(Watcher): A function that receivesargument
from the event.
Returns
Subscription: Unsubscribe function.
Examples
import { createEvent } from "effector";
const sayHi = createEvent();
const unwatch = sayHi.watch((name) => console.log(`${name}, hi there!`));
sayHi("Peter"); // => Peter, hi there!
unwatch();
sayHi("Drew"); // => nothing happened
.subscribe(observer)
This is the low-level method to integrate event with the standard Observable
pattern.
You don’t need to use this method on your own. It is used under the hood by rendering engines or so on.
Read more:
Properties
These set of property is mostly set by effector/babel-plugin
or @effector/swc-plugin
. So they are exist only when babel or SWC is used.
.sid
It is an unique identifier for each event.
It is important to note, SID is not changes on each app start, it is statically written inside your app bundle to absolutely identify units.
It can be useful to send events between workers or server/browser: examples/worker-rpc.
It has the string | null
type.
.shortName
It is a string
type property, contains the name of the variable event declared at.
import { createEvent } from "effector";
const demo = createEvent();
// demo.shortName === 'demo'
But reassign event to another variable changes nothing:
const another = demo;
// another.shortName === 'demo'
.compositeName
This property contains the full internal chain of units. For example, event can be created by the domain, so the composite name will contain a domain name inside it.
import { createEvent, createDomain } from "effector";
const first = createEvent();
const domain = createDomain();
const second = domain.createEvent();
console.log(first);
// => { shortName: "first", fullName: "first", path: ["first"] }
console.log(second);
// => { shortName: "second", fullName: "domain/second", path: ["domain", "second"] }
Types
import { type EventPayload } from "effector";
EventPayload<E>
Extracts type of payload from Event
or EventCallable
.
const event: Event<Payload>;
type Payload = EventPayload<typeof event>;