Skip to content

Instantly share code, notes, and snippets.

@ilyalesik
Created August 13, 2019 17:45
Show Gist options
  • Save ilyalesik/94f0172b55659f22d2ec7643e450ca2a to your computer and use it in GitHub Desktop.
Save ilyalesik/94f0172b55659f22d2ec7643e450ca2a to your computer and use it in GitHub Desktop.
Article "Why I choose Effector instead of Redux or MobX?"

Effector is a brand new reactive state manager. He has an ambitious team that aims to make a state manager who solves all the problems of existing solutions. Writing the core of the library took six months and several attempts when the team started development from scratch. They recently released the first stable release 19.0.0.

At this article, I will show why I prefer using Effector for new projects instead of other state managers. Let's started from Effector API.

Stores and events

Effector use two intuitive concepts: store and event.

A store is an object that holds some value. We can create stores with createStore method:

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 and it's possible to create as many repositories as needed.

So how to change store? Events! We can create events with createEvent method and subscribe store on them:

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 event and return new store value
  .on(decrement, state => state - 1)  
  .reset(resetCounter)

counter.watch(console.log)

Event is like "action" at Redux terminology, and .on is like reducer. Events are just functions and can be triggered by any place of the code.

Effector implements reactive programming paradigm. Events and stores can be considered as reactive entities (streams, in other words), they have .watch method which allows subscribing on events and store changes.

Integrate with React?

Integration with React is easy. A store can be connected to the component through useStore method from effector-react package. Events can be passed as event handlers (onClick, etc.)

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>{value}</div>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
      <button onClick={resetCounter}>reset</button>
    </>
  )
}

const App = () => <Counter />

Integration with other frameworks

Vue

There is effector-vue package.

Svelte

Effector nodes are compatible with Observable, so for integration with Svelte is not even needed any package. Only $ symbol before variable at template needed:

// Counter.svelte
<script context="module">
  import effector from 'effector/effector.umd.js';

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

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

Count: {$increment}
<br />
<button on:click={increment}>Increment</button>

Side effects

Effector has a convenient wrapper for side effects - createEffect. They allow to wrap an async functions and subscribe to 'done' and 'fail' events.

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

// OR
getUser.use(params => {
  return fetch(`https://example.com/get-user/${params.id}`)
    .then(res => res.json())
})

const users = createStore([]) // <-- Default state
  // add reducer for getUser.done event (fires when promise resolved)
  .on(getUser.done, (state, {result: user, params}) => [...state, user])

So with Effector you don't need additional package for calling side effects like 'redux-thunk' or 'redux-saga'.

Advanced usage: combine, map

The awesome feature of Effector is a computed stores. Computed stores can be created with combine function and map method of the store. This allows to subscribe only on changes that real needed. For React apps performance important make updates only at actual changes affected. So Effector help with it.

combine creates a new store from several exciting stores:

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 - creates a derived store:

const title = createStore("")
const changed = createEvent()

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

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

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

changed("hello") // new length 5
changed("world") // 
changed("hello world") // new length 11

Comparison with other state managers

So let's compare Effector with other state managers.

Redux

  • Multiple stores at Effector instead single store of Redux. Multiple stores are more convenient in many situations.
  • Effector is less verbose that Redux. And requires a less boilerplate code.
  • Effector has much better type support out of the box. Most of Effector function have good type inference.
  • Redux have better dev tools right now, but Effector have huge potential for creating awesome dev tools - Effector team has plans for creating visual dev tools, that represent application as a graph of connected stores and events.

MobX

  • Effector has a much smaller bundle size. 14.9kb at MobX vs 5.9kb at Effector (minified + gziped).
  • MobX has a much more magic inside.
  • Hard to separate model and view.
  • Runtime semantic and mutable state (is not a better way for debugging).
  • Proxy pattern is lack of visual part of code semantic.
  • Maybe difficult to use with custom data-structures.

RxJS

  • RxJS does not specialize in state management, so Effector has more convenient API for this.
  • RxJS is not 'glitch-free', it's demonstrated at this test.

Effector advantages

I promised to tell you why I chose the Effector. There are many reasons for this:

  • Expressive and laconic API.
  • Reactive way.
  • Stable and production ready.
  • Great performance, also I don't see any lack of memory.
  • Motivated team, great community.

Conclusion

Effector is not a silver bullet, but if you find a fresh view on state managers it can be a good option. Do not be afraid to try something new and choose not the most popular solutions. Interested? Let's try Effector!

Thanks

  • Andrey Sitnik @ai - article promotion
  • Alexander Kladkov @A1992 - errors finding
  • Artyom Arutyunyan @artalar - errors finding

Links

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment