Nowadays, frontend applications build with using redux(rtk)/mobx state-managers because they modern and hype trends.
Effector, it is the another way to create state and business logic for your frontend application.
In this article, I just want to introduce you with effector!
⚙️ How to use
Effector has three main units: stores, effects, events.
Seems simple? But not everything is as simple as it seems🙂
☄️ Stores
The Store is an object which stores the state value.
You can create store using createStore(initialState)
const $store = createStore(1);
Now we have $store
with numeric state, which has value 1
☄️ Events
The Event is a simple function with one argument which returns this argument, but also this unit prepared for another effector operations (will be mentioned below). Better to think that this is intention to change the state.
In general there are two types of events:
- something happened on UI, e.g.
loginButtonPressed
,inputValueChanged
- Event which changes the state, e.g.
loginInUser
,changeInputValue
You can create event using createEvent(name?)
const changeStore = createEvent<number>()
Now we have changeStore
event with numeric payload.
Let's create a bit more events for our example
const incrementButtonPressed = createEvent()
const decrementButtonPressed = createEvent()
☄️ Effects
The Effect is a container for async function. If we compare it with Redux Toolkit, we can relate this unit to async thunk.
Effects in Effector are used to create and handle async operations or side effects
Effects should be used for impure actions, e.g. AJAX, working with localStorage, some code which can throw an exception.
In general, you will use effects for making requests.
You can create effect using createEffect(handler)
const sendStateToServerFx = createEffect(async (state: number) => {
const response = await fetch('/server', {
method: 'post',
body: state
})
})
Now we have sendStateToServerFx
effect with numeric payload
Let's to sum up that we had managed to create
const $store = createStore(1);
const changeStore = createEvent<number>();
const incrementButtonPressed = createEvent()
const decrementButtonPressed = createEvent()
const sendStateToServerFx = createEffect(async (state: number) => {
const response = await fetch('/server', {
method: 'post',
body: state
})
})
How to connect it all together ?
Effector has a lot of methods to work with data flow, but in this article we will try to use only one sample
method
import { sample } from "effector"
sample
it is universal multipurpose method to work with data flow
Now we need to put value from changeStore
payload to $store
store
sample({
source: changeStore,
target: $store,
})
This operation says "when source will be called, take value from source and send it to target"
Now if we call changeStore(100)
then $store
will have value 100
in state
Now we need send $store
value to server when it will be changed
sample({
source: $store,
target: sendStateToServerFx,
})
Two samples, but what if we can create only sample ? Let's try
sample({
source: changeStore,
target: [sendStateToServerFx, $store],
})
But what if we need to prevent this changes when sendStateToServerFx
is in pending state (request is calling/response is fetching)
sample({
source: sendStateToServerFx.pending,
clock: changeStore,
filter: (pending, value) => !pending,
fn: (pending, value) => value,
target: sendStateToServerFx,
})
This operation says "when clock will be called, take value from source and send it to the filter. If filter returns true
then call fn and returned value from fn send to target, otherwise skip fn and target steps"
Also we forget about increment
and decrement
events, let's connect them to our store
sample({
source: $store,
clock: incrementButtonPressed,
fn: (state) => state + 1,
target: changeStore,
})
sample({
source: $store,
clock: decrementButtonPressed,
fn: (state) => state - 1,
target: changeStore,
})
I think now sample
looks a little more complicated than it used, but it is very powerful!
Here is a link to effector playground to check this code
So, let's connect this to React
import { FC } from "react";
import { useUnit } from "effector-react";
const MyComponent: FC = () => {
const state = useUnit($store)
return (
<div>
<button onClick={() => decrementButtonPressed()}>
-1
</button>
<span>
{state}
</span>
<button onClick={() => incrementButtonPressed()}>
+1
</button>
</div>
)
}
Now we have made the basic counter with one specific thing - safety sending counter data to server.
And React knows about events and data-to-render only, we don't need to create payload for events\effects inside React, because we can store this logic inside effector.
📦 What about size?
Core library package has 10.4KB gzipped size (bundlephobia)
React bindings package has 3.7KB gzipped size (bundlephobia)
Summarize
Effector has a lot of methods to create data-flow in your frontend application (attach, combine, merge, restore, guard, forward etc).
Using this library, you can declare all business logic for your frontend application. Even complex cases.
You can separate UI-logic from business logic with using this library.
Currently, many companies actively use the effector as data-flow\state manager in their own frontend applications.
I hope you will try this powerful tool ❤️
Thanks for reading, good luck!
Top comments (1)
thanks