Why do I choose Effector instead of Redux or MobX?
Tags: javascript, effector, react
Published: 2019-07-08 08:31:34

[Effector](https://effector.now.sh/) is a brand new reactive state manager. Its ambitious team aims to solve all the problems that existing solutions have. Writing the core of the library from scratch took several attempts across six months, and recently the team released the first [stable release](https://github.com/zerobias/effector/releases/tag/effector%4019.0.0).

In this article, I will show why I prefer using Effector for my new projects instead of other state managers. Let's get started with the Effector API.

## Basics
Effector uses two concepts you might already be familiar with: *store* and *event*. 

A *store* is an object that holds some value. We can create stores with the `createStore` helper:
```js
import {createStore} from 'effector'

const counter = createStore(0) // create store with zero as default value

counter.watch(console.log) // watch store changes
```
Stores are lightweight, so whenever you need to introduce some state to your app, you simply create a new store. 

So how do we update our store? Events! You create events with the `createEvent` helper and have your store updated by reacting *on* them:

```js
import {createStore, createEvent} from 'effector'

const increment = createEvent('increment')
const decrement = createEvent('decrement')
const resetCounter = createEvent('reset counter')

const counter = createStore(0)
  .on(increment, state => state + 1) // subscribe to the event and return new store value
  .on(decrement, state => state - 1)  
  .reset(resetCounter)

counter.watch(console.log)
```
Event is like an "action" in Redux terms, and `store.on(trigger, handler)` is somewhat like `createStore(reducer)`. Events are just functions that can be called from any place in your code.

Effector implements the Reactive Programming paradigm. Events and stores are considered as reactive entities (*streams*, in other words), they have a `watch` method which allows subscribing to events and store changes. 

### Integration with React
A component can connect to the store by calling the `useStore` hook from `effector-react` package. Effector events can be passed to child React elements as event handlers (`onClick`, etc.)

```js
import React from 'react'
import ReactDOM from 'react-dom'
import {createStore, createEvent} from 'effector'
import {useStore} from 'effector-react'

const increment = createEvent('increment')
const decrement = createEvent('decrement')
const resetCounter = createEvent('reset counter')

const counter = createStore(0)
  .on(increment, state => state + 1)
  .on(decrement, state => state - 1)
  .reset(resetCounter)

counter.watch(console.log)

const Counter = () => {
  const value = useStore(counter) // subscribe to store changes

  return (
    <>
      <div>Count: {value}</div>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
      <button onClick={resetCounter}>reset</button>
    </>
  )
}

const App = () => <Counter />
const div = document.createElement('div')
document.body.appendChild(div)
ReactDOM.render(
  <App/>,
  div
)
```

### Integration with other frameworks
### Vue
There is [effector-vue](https://effector.now.sh/en/api/effector-vue/effector-vue) package.
### Svelte
Effector stores are *Observable*, so you don't need any additional packages to use them in Svelte. Simply prepend `$` to the store's name in your template:
```js
// Counter.svelte
<script context="module">
  import effector from 'effector/effector.umd.js';

  export const increment = createEvent('increment')
  export const decrement = createEvent('decrement')
  export const resetCounter = createEvent('reset counter')
  export const counter = effector.createStore(0)
    .on(increment, (n) => n + 1)
    .on(decrement, state => state - 1)
    .reset(resetCounter)
</script>

// App.svelte
<script>
  import { counter, increment, decrement, resetCounter } from './Counter.svelte'
</script>

<div>Count: {$counter}</div>
<button on:click={increment}>+</button>
<button on:click={decrement}>-</button>
<button on:click={resetCounter}>reset</button>

```

## Side effects
With Effector you don't need thunks or sagas to handle side effects. Effector has a convenient helper called `createEffect` that wraps an async function and creates three events that your store can subscribe to: an initializer (the effect itself) and two resolvers called `done` and `fail`.
```js
const getUser = createEffect('get user');
getUser.use(params => {
  return fetch(`https://example.com/get-user/${params.id}`)
    .then(res => res.json())
})

// OR

const getUser = createEffect('get user', {
  handler: params => fetch(`https://example.com/get-user/${params.id}`)
    .then(res => res.json())
})

const users = createStore([]) // <-- Default state
  // getUser.done is the event that fires whenever a promise returned by the effect is resolved
  .on(getUser.done, (state, {result, params}) => [...state, result])
``` 

## Advanced usage: `combine`, `map`
One of the awesome features of Effector is computed stores. Computed stores can be created using either the `combine` helper or `.map` method of the store. This allows subscribing only to changes that *matter* to the particular component. In React apps, performance may be heavily impacted by unnecessary state updates, so Effector helps eliminate them.

`combine` creates a new store that calculates its state from several existing stores:
```js
const balance = createStore(0)
const username = createStore('zerobias')

const greeting = combine(balance, username, (balance, username) => {
  return `Hello, ${username}. Your balance is ${balance}`
})

greeting.watch(data => console.log(data)) // Hello, zerobias. Your balance is 0
``` 
`map` allows creating derived stores:
```js
const title = createStore("")
const changed = createEvent()

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

title.on(changed, (oldTitle, newTitle) => newTitle)

length.watch((length) => console.log("new length is ", length)) // new length is 0

changed("hello") // new length is 5
changed("world")
changed("hello world") // new length is 11
```
## Comparison with other state managers

### Redux
* Most projects that use Redux implement the whole application state in a single store. Having multiple stores isn't forbidden, but doing this right is kind of tricky. Effector is *built* to work with *lots* of different stores simultaneously.
* Redux is very explicit but very verbose as well. Effector requires less boilerplate code, but all state dependencies are still explicit.
* Redux was originally written in pure JS and without static typing in mind. Effector has much wider typing support out of the box, including type inference for most helpers and methods.
* Redux has great dev tools. Effector somewhat lags right now, but the team already has plans for dev tools that visually represent your application as a graph of connected stores and events.

### MobX
* When minified and gzipped, MobX is almost 20kb ([14.9kb](https://bundlephobia.com/result?p=mobx@5.13.0) + [4.6kb](https://bundlephobia.com/result?p=mobx-react@6.1.3) for React bindings), while Effector is less than 8 kb ([5.8 kb](https://bundlephobia.com/result?p=effector@20.1.2) + [1.7 kb](https://bundlephobia.com/result?p=effector-react@20.2.2) for React).
* MobX has a lot of *magic* inside: implicit subscriptions to observable data changes, "mutable" state objects that use Proxies under the hood to distribute updates, etc. Effector uses immutable state, explicitly combines stores' state and only allows changing it through events.
* MobX encourages keeping your data model close to the view. With Effector, you can completely isolate the data model and keep your UI components' API clean & simple.
* May be difficult to use with custom data-structures.

### RxJS
* Strictly speaking, although RxJS solves many tasks, it's a reactive extensions library, not a state management tool. Effector, on the other hand, is designed specifically for managing application state and has a small API that is easy to learn.
* RxJS is not 'glitch-free'. In particular, synchronous streams for computed data do not produce consistent updates by default: see [an example](https://codesandbox.io/s/effector-comparison-r9qy2) on how different reactive state management tools handle this task.

## Why did I choose Effector
Here's a list of things I consider to be Effector's advantages over most similar tools:
* Expressive and laconic API.
* Reactive programming paradigm at its core.
* Stable, production-ready.
* Great performance, also I don't see any memory leaks.
* Motivated team, great community.

## Conclusion
Effector is not a *silver bullet*, but it's certainly a fresh take on state management. Do not be afraid to try something new and diverge from the most popular solutions. Interested? Try [Effector](https://effector.now.sh/) now!

## Thanks
* Andrey Sitnik [@ai](https://github.com/ai) - article promotion
* Alexander Kladkov [@A1992](https://t.me/A1992) - fact checking
* Artyom Arutyunyan [@artalar](https://github.com/artalar/) - fact checking
* [Alexander Chudesnov](https://chudesnov.tech) - proofreading, editing

## Links
* [Effector GitHub](https://github.com/zerobias/effector)
* [Effector Twitter](https://twitter.com/EffectorJS)
* [Effector documentation](https://effector.now.sh/en/introduction/installation)
* [awesome-effector](https://github.com/ilyalesik/awesome-effector) - Effector awesome list.
* [Effector: powerful and fast 5kb state manager](https://codeburst.io/effector-state-manager-6ee2e72e8e0b)
* [Effector — State Manager You Should Give a Try](https://itnext.io/effector-state-manager-you-should-give-a-try-b46b917e51cc)
* [The introduction to Reactive Programming you've been missing](https://gist.github.com/staltz/868e7e9bc2a7b8c1f754)
* [The Reactive Manifesto](https://www.reactivemanifesto.org/)
* [flaxom](https://github.com/artalar/flaxom)