Is Effector a Redux killer? Tutorial from scratch. Part 1

Author: Maslov AndreyFront-end developer.
Reading time:~10 minutes

business logic with ease.

Content:

  1. About the article.

  2. Why should you use effector ?

  3. Concept.

  4. Useful and basic from api.

  5. How the Effector core works, in simple terms.

  6. Results.

  7. Useful materials.

About the article

With this article, I open a tutorial from three subsequent articles on Effector JS – not only a convenient state manager, but also the most powerful of the tools today (in my personal opinion).

Part #1 will be an introduction to the tool so you can understand if you need an Effector or not. Let’s analyze the main features and touch on how the core of the library works.

Why should you use an Effector ?

  • No more boilerplate code.

  • The application adopts an initially extensible architecture (because the effector allows you to isolate business logic across processes, it’s also worth mentioning here feature-sliced ​​architecturewhich we will discuss in later articles).

  • A convenient and large API that will save the developer from many routine things.

  • Business logic is no longer “smeared” across controller files, but isolated across processes, interfaces do not intersect with logic (similar to the implementation of MV * patterns).

  • No magic, everything is built on graphs and subscriptions (we’ll talk about this at the end of this article).

  • There is a Russian-speaking community, detailed documentation in Russian and English.

  • Constant support, releases with fixes and new features.

  • Lightweight and speed.

  • Typescript support.

Concept

The work of the entire state manager is provided by three main units:

Store

An object to store data.

createStore – the function of creating a store, the name is usually started with the $ sign.

event

This unit is the dominant controlling entity. The event triggers reactive calculations in the application.

createEvent – event creation function.

You can subscribe your store to any events in which all components dependent on this store will be updated, you can do this using .on, passing the unit as the first argument to the method, and the callback function as the second, which will return the result of the store change.

Example:

//init.js

export const eventPlus = createEvent()
export const eventMinus = createEvent()

export const $storeCounter = createStore(0)
  .on(eventPlus, (store) => store + 1)
  .on(eventMinus, (store) => store - 1)

//components.jsx

export Component = () => {
  const count = useStore($storeCounter) 
    //От этого хука можно будет отказаться, 
    //при использовании effector/reflect (рассмотрим в последующих туториалах) 

  return (
    <h1>{ count }</h1>
  )
}

Agree, it looks very concise and simple.

effects

This unit is very similar to the previous one, with the only exception that the effect creates a chain of event calls, which actually consist of event. Typically, this unit is used when working with asynchronous functions.

createEffect – effect creation function, takes the first arguments of the callback function – effect call handler, the name of such a function usually ends with Fx.

Example:

//api.js

export const getCount = (payload) => {
  return axios.get('/count', payload)
}

//init.js

export const getCountFx = createEffect(getCount)

Effect provides many events like doneData, failData, pending, etc. (More details can be found in documentation).

Below I will give an example of working with an effect and its capabilities, it’s worth saying right away that working with a variant from effector / reflect will facilitate the processing of component states, but as I mentioned earlier, we will analyze it in the following articles.

//init.js

export const $count = createStore(0)
  .on(getCountFx.doneData, (_store, res) => res.data.count)

//components.jsx

export Component = () => {
  const count = useStore($count)  

  if (getCountFx.pending) {
    return <h1>Loading...</h1>
  }

  if (getCountFx.failData) {
    return <h1>Error</h1>
  }

  return (
    <h1>{ count }</h1>
  )
}

Useful and basic from api

combine – allows you to combine several stores and create one derivative.

Let’s create a store that will store the boolean value of the submit button disable if the request to get the counter is in the “pending” status or if this request ended in a catch block. The third argument is optional and serves to transform the state.

const $submitDisabled = combine(
  getCountFx.pending,
  getCountFx.failData,
  (pending, faildData) => pending || faildData
)

forward – creates a connection between units, which we dealt with a little higher.

Let’s write the code that will display an error.
Forward takes an object with two fields, from and to, which expect units (or arrays of units), when executing from , the to unit will be called.

const showErrorFx = createEffect(() => showToast('Something went wrong'))

forward({
  from: getCountFx.failData,
  to: showErrorFx
})

guard – a method that allows you to run units on a condition.
Let’s write a code that will send a form request to the backend if the form is valid.

guard({
  clock: sendEvent, //юнит, при срабатывании которого будет выполняться filter
  filter: $isValid, //дальнейший вызов target возможен при filter = true
  source: $form, //данные, которые будут передаваться в target
  target: submitFormFx // юнит, который будет вызван при вызове clock и истинном значении filter
})

sample – a method, the principle of operation is the same as that of guard, the argument fn is added – a callback, the result of which will be transferred to target.

sample({ source?, clock?, filter?, fn?, target?})

The Effector API provides a wide variety of methods, above you see only those that I used on the project more often than others, and this is a small number of available ones, be sure to consider the following methods: is, restore, split, attach. Also read about the previously unconsidered unit domain.

Just about how the Effector core works.

The basis is a graph breadth traversal, where the vertices of the graph are queued events that are stored in a kernel object, looks like this:

export type Node = {
  id: ID
  next: Array<Node>
  seq: Array<Cmd>
  scope: {[key: string]: any}
  meta: {[tag: string]: any}
  family: {
    type: 'regular' | 'crosslink' | 'domainn'
    links: Node[]
    owners: Node[]
  }
}

Consider the three main properties of an object:

next – array of graph edges (links to next vertices)
seq – an array with a sequence of steps
scope – the data object required for the steps to work

When data enters the core (for example, by calling event), a chain of events is launched that updates all related stores, and the components themselves. When a node enters the queue, we perform steps from seq (calculations, filtering, moving the node to standby mode, and others, I will leave a link in the materials for full review)

The scheme of the kernel.  Graph traversal.
The scheme of the kernel. Graph traversal.
The scheme of the kernel.  Graph traversal.
The scheme of the kernel. Graph traversal.
The scheme of the kernel.  Graph traversal.
The scheme of the kernel. Graph traversal.

By the way, this is a simplified model of the effector. So, for example, there are 5 queues in the kernel, not 1. All these queues differ in priority for execution.

Results

When I first used this library, I had a double feeling, because why should I refuse the same Redux or MobX, “everything is fine”, only after half a year of dense use of effector on the project, I began to notice that it became easier and faster to develop, and the code became structured and understandable.

Although there is little information on the Internet about different solutions to make it easier for a beginner to roll in, all this is covered by a wonderful community that is always ready to help, for example, in the tg channel. And with this series of articles, I will try to make the process of immersion quick and easy.

You should not engage in holivars in the comments, do not take the article as the only and true source of truth. Leave comments on misunderstandings, we will find the truth together 😀

In this article, we have analyzed only a tiny part of what the effector can do, so please go over some more materials to consolidate.

Next, we will deploy a real application in the TypeScript + Effector bundle (in the documentation, all examples are in js, so for beginners, using ts may not be the most pleasant thing), we will talk about feature-sliced ​​architecture and best practices.

Fixing materials:

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *