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.
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.
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 />
There is effector-vue package.
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>
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'.
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
So let's compare Effector with other state managers.
- 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.
- 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 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.
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.
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!