DEV Community

Cover image for So what is Effector ☄️ ?
Sergey S. Volkov
Sergey S. Volkov

Posted on

So what is Effector ☄️ ?

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);
Enter fullscreen mode Exit fullscreen mode

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>()
Enter fullscreen mode Exit fullscreen mode

Now we have changeStore event with numeric payload.

Let's create a bit more events for our example

const incrementButtonPressed = createEvent()
const decrementButtonPressed = createEvent()
Enter fullscreen mode Exit fullscreen mode

☄️ 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
  })
})
Enter fullscreen mode Exit fullscreen mode

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
  })
})
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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,
})
Enter fullscreen mode Exit fullscreen mode

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,
})
Enter fullscreen mode Exit fullscreen mode

Two samples, but what if we can create only sample ? Let's try

sample({
  source: changeStore,
  target: [sendStateToServerFx, $store],
})
Enter fullscreen mode Exit fullscreen mode

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,
})
Enter fullscreen mode Exit fullscreen mode

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,
})

Enter fullscreen mode Exit fullscreen mode

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>
  )
}
Enter fullscreen mode Exit fullscreen mode

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)
10.4KB gzipped size
React bindings package has 3.7KB gzipped size (bundlephobia)
3.7KB gzipped size

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 (0)