effector vs mobx vs react-query vs recoil vs redux vs redux-saga vs redux-thunk vs xstate vs zustand
State Management and Data Fetching Solutions for React Applications
effectormobxreact-queryrecoilreduxredux-sagaredux-thunkxstatezustandSimilar Packages:

State Management and Data Fetching Solutions for React Applications

effector, mobx, react-query, recoil, redux, redux-saga, redux-thunk, xstate, and zustand are libraries that help manage state and side effects in JavaScript applications, particularly in React. They address different aspects of state management: global state (redux, zustand, recoil, mobx, effector), asynchronous data fetching and caching (react-query), complex state machines (xstate), and middleware for handling side effects in Redux-based apps (redux-thunk, redux-saga). Each offers distinct mental models, APIs, and trade-offs around predictability, boilerplate, reactivity, and developer experience.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
effector04,8191.59 MB1515 months agoMIT
mobx028,1774.35 MB855 months agoMIT
react-query048,5862.26 MB1673 years agoMIT
recoil019,5262.21 MB3223 years agoMIT
redux061,441290 kB432 years agoMIT
redux-saga022,4886.25 kB454 months agoMIT
redux-thunk017,72826.8 kB12 years agoMIT
xstate029,2582.25 MB16611 days agoMIT
zustand057,09695 kB423 days agoMIT

State Management Deep Dive: effector, mobx, react-query, recoil, redux, redux-saga, redux-thunk, xstate, zustand

Managing state in modern JavaScript apps goes far beyond useState. The libraries in this comparison solve different problems: some handle local UI state, others manage server data, complex workflows, or global application state. Let’s break down how they work in practice.

🧠 Core Mental Models: How Each Library Thinks About State

redux – Predictable State Container

Redux enforces a single source of truth with immutable updates via pure reducer functions. Every change is triggered by a plain action object.

// redux
import { createStore } from 'redux';

const initialState = { count: 0 };
const reducer = (state = initialState, action) => {
  if (action.type === 'increment') return { count: state.count + 1 };
  return state;
};

const store = createStore(reducer);
store.dispatch({ type: 'increment' });

zustand – Minimalist Hook-Based Store

Zustand skips reducers and actions. You define state and methods together in a single hook-like function.

// zustand
import { create } from 'zustand';

const useStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 }))
}));

// In component
const { count, increment } = useStore();

recoil – Atomic State Graph

Recoil models state as atoms (units) and selectors (derived state). Components subscribe only to what they use.

// recoil
import { atom, selector, useRecoilState, useRecoilValue } from 'recoil';

const countAtom = atom({ key: 'count', default: 0 });
const doubledCount = selector({
  key: 'doubledCount',
  get: ({ get }) => get(countAtom) * 2
});

// In component
const [count, setCount] = useRecoilState(countAtom);
const double = useRecoilValue(doubledCount);

mobx – Transparent Reactive Programming

MobX wraps state in observables. When you mutate them, all observers (like React components) update automatically.

// mobx
import { makeAutoObservable } from 'mobx';
import { observer } from 'mobx-react-lite';

class Counter {
  count = 0;
  constructor() { makeAutoObservable(this); }
  increment() { this.count += 1; }
}

const counter = new Counter();

// In component (must be observer)
const CounterComponent = observer(() => <div>{counter.count}</div>);

effector – Event-Driven Reactive System

Effector uses stores (state), events (triggers), and effects (async operations). It’s framework-agnostic and composable.

// effector
import { createEvent, createStore, createEffect } from 'effector';

const increment = createEvent();
const $count = createStore(0).on(increment, (state) => state + 1);

// In React
import { useUnit } from 'effector/react';
const Counter = () => {
  const [count, inc] = useUnit([$count, increment]);
  return <button onClick={inc}>{count}</button>;
};

xstate – Explicit State Machines

XState defines states and allowed transitions. Invalid transitions are impossible by design.

// xstate
import { createMachine, interpret } from 'xstate';

const toggleMachine = createMachine({
  id: 'toggle',
  initial: 'inactive',
  states: {
    inactive: { on: { TOGGLE: 'active' } },
    active: { on: { TOGGLE: 'inactive' } }
  }
});

const service = interpret(toggleMachine).start();
service.send('TOGGLE'); // moves to 'active'

react-query – Server-State Synchronization

React Query doesn’t manage UI state. It handles fetching, caching, and syncing data from APIs.

// react-query
import { useQuery, useMutation } from '@tanstack/react-query';

// Fetching
const { data, isLoading } = useQuery({
  queryKey: ['todos'],
  queryFn: () => fetch('/api/todos').then(res => res.json())
});

// Mutating
const mutation = useMutation({
  mutationFn: (newTodo) => fetch('/api/todos', { method: 'POST', body: newTodo }),
  onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] })
});

redux-thunk – Simple Async Middleware

Thunks let action creators return functions that dispatch actions asynchronously.

// redux-thunk
const fetchUser = (id) => async (dispatch) => {
  dispatch({ type: 'FETCH_USER_START' });
  try {
    const user = await api.getUser(id);
    dispatch({ type: 'FETCH_USER_SUCCESS', payload: user });
  } catch (err) {
    dispatch({ type: 'FETCH_USER_ERROR', payload: err });
  }
};

// Dispatch
store.dispatch(fetchUser(123));

redux-saga – Complex Side Effect Management

Sagas use generator functions to describe side effects declaratively, making them highly testable.

// redux-saga
import { call, put, takeEvery } from 'redux-saga/effects';

function* fetchUser(action) {
  try {
    const user = yield call(api.getUser, action.payload.id);
    yield put({ type: 'FETCH_USER_SUCCESS', payload: user });
  } catch (err) {
    yield put({ type: 'FETCH_USER_ERROR', payload: err });
  }
}

function* watchFetchUser() {
  yield takeEvery('FETCH_USER_REQUEST', fetchUser);
}

⚙️ Handling Asynchronous Logic

Not all state libraries handle async the same way:

  • react-query is purpose-built for async data. It manages loading states, retries, caching, and background updates automatically.
  • redux-thunk is the simplest way to do async in Redux but offers no advanced control.
  • redux-saga gives full control over complex async flows (e.g., race conditions, cancellation) but requires learning generators.
  • effector has first-class createEffect for async with built-in pending/error states.
  • mobx lets you write async logic directly in methods using async/await.
  • zustand supports async in setters just like regular functions.
  • xstate can invoke promises or callbacks during state transitions.
  • recoil uses selectorFamily with async functions, but error/loading states must be managed manually.
  • redux alone cannot handle async — it requires middleware like thunk or saga.

📦 Integration with React

  • Tight coupling: recoil, react-query, and mobx-react-lite are React-specific.
  • Framework agnostic: effector, xstate, redux, zustand (though commonly used with React) work anywhere.
  • Provider requirements: redux, mobx, and recoil require context providers at the root. zustand and effector do not.

🔁 Performance Characteristics

  • Granular updates: recoil, mobx, zustand, and effector avoid unnecessary re-renders by letting components subscribe to specific state slices.
  • Global re-renders: Vanilla redux causes all connected components to re-render on any state change unless you use React.memo or createSelector.
  • Computed values: recoil selectors, mobx computed, and effector derived stores cache results until dependencies change.

🛠️ Boilerplate and Learning Curve

LibraryBoilerplateLearning Curve
zustandVery LowLow
mobxLowMedium
react-queryLowLow
recoilMediumMedium
effectorMediumMedium-High
xstateMediumHigh
redux (+RTK)MediumMedium
redux-thunkLowLow
redux-sagaHighHigh

🧩 When to Combine Libraries

It’s common — and often wise — to use more than one:

  • redux + react-query: Use Redux for UI state and React Query for server state.
  • zustand + xstate: Use Zustand for simple global state and XState for complex feature flows.
  • mobx + react-query: MobX for client state, React Query for API data.

Avoid mixing multiple global state managers (e.g., Redux + Zustand) unless you have a clear boundary.

✅ Summary: Key Strengths

LibraryBest For
effectorFramework-agnostic reactive systems
mobxMutable-style code with auto-tracking
react-queryServer data caching and synchronization
recoilFine-grained React state with derived values
reduxPredictable, debuggable global state
redux-sagaComplex, testable side effects in Redux
redux-thunkSimple async logic in Redux
xstateVisualizable, safe state transitions
zustandLightweight, hook-based global state

💡 Final Guidance

  • If you’re fetching data from APIs, start with react-query — it solves 80% of async data problems out of the box.
  • For global UI state in React, zustand is the sweet spot for most teams: minimal setup, no providers, great performance.
  • Need strict predictability and dev tools? redux with Redux Toolkit remains a solid choice.
  • Building a wizard, modal flow, or device interface? Model it with xstate to eliminate invalid states.
  • Avoid redux-thunk for anything beyond basic API calls; prefer redux-saga or switch to a non-Redux solution if async logic grows.

Choose based on your problem, not trends. Most apps don’t need all nine — pick one or two that match your actual complexity.

How to Choose: effector vs mobx vs react-query vs recoil vs redux vs redux-saga vs redux-thunk vs xstate vs zustand

  • effector:

    Choose effector if you want a reactive, event-driven architecture that works outside React and supports fine-grained updates with minimal re-renders. It’s well-suited for complex logic that needs to be decoupled from UI frameworks, but requires learning its unique concepts like stores, events, and effects.

  • mobx:

    Choose mobx if you prefer writing mutable-like code that automatically tracks dependencies and updates observers. It reduces boilerplate significantly and works well for medium-sized apps where performance isn’t bottlenecked by frequent small updates, but it can make debugging harder due to implicit reactivity.

  • react-query:

    Choose react-query when your app heavily relies on server-state (data from APIs) and you need built-in caching, background refetching, mutations, and optimistic updates. It’s not a global state manager for UI state, but excels at synchronizing client and server data with minimal manual effort.

  • recoil:

    Choose recoil if you’re building a React-only app and want atomic, selector-based state that scales from simple to complex without context bottlenecks. It integrates deeply with React’s concurrent features, but is tightly coupled to React and may add complexity for teams unfamiliar with its atom/selector model.

  • redux:

    Choose redux if you need a predictable, centralized store with strict unidirectional data flow, time-travel debugging, and strong ecosystem support. It’s ideal for large teams or apps requiring auditability, but comes with significant boilerplate unless paired with modern tooling like Redux Toolkit.

  • redux-saga:

    Choose redux-saga when managing complex, long-running side effects (like websockets or retry logic) in a Redux app. It uses generator functions for testable, declarative control flow, but has a steep learning curve and adds considerable complexity for simple use cases.

  • redux-thunk:

    Choose redux-thunk for basic async logic in Redux apps, like simple API calls that dispatch actions before and after. It’s lightweight and easy to learn, but lacks advanced features for complex workflows — making it suitable only for straightforward side effects.

  • xstate:

    Choose xstate when your feature involves explicit, visualizable state transitions (e.g., wizards, modals, or device control). Its finite state machine model prevents invalid states and improves maintainability for complex flows, but introduces overhead for simple CRUD-style state.

  • zustand:

    Choose zustand for a lightweight, hook-based global state solution with minimal boilerplate and no context providers. It’s ideal for most React apps needing shared state without Redux’s ceremony, offers great performance via selective subscriptions, and supports middleware and persistence out of the box.

README for effector

Effector Comet Logo


join gitter build status discord chat become a patron Ask DeepWiki

☄️ effector

Business logic with ease

Visit effector.dev for docs, guides and examples

Table of Contents

Introduction

Effector implements business logic with ease for Javascript apps (React/React Native/Vue/Svelte/Node.js/Vanilla), allows you to manage data flow in complex applications. Effector provides best TypeScript support out of the box.

Effector follows five basic principles:

  • Application stores should be as light as possible - the idea of adding a store for specific needs should not be frightening or damaging to the developer.
  • Application stores should be freely combined - data that the application needs can be statically distributed, showing how it will be converted in runtime.
  • Autonomy from controversial concepts - no decorators, no need to use classes or proxies - this is not required to control the state of the application and therefore the api library uses only functions and plain js objects
  • Predictability and clarity of API - a small number of basic principles are reused in different cases, reducing the user's workload and increasing recognition. For example, if you know how .watch works for events, you already know how .watch works for stores.
  • The application is built from simple elements - space and way to take any required business logic out of the view, maximizing the simplicity of the components.

Installation

You can use any package manager

npm add effector

React

To getting started read our article how to write React and Typescript application.

npm add effector effector-react

SolidJS

npm add effector effector-solid

Vue

npm add effector effector-vue

Svelte

Svelte works with effector out of the box, no additional packages needed. See word chain game application written with svelte and effector.

CDN

Documentation

For additional information, guides and api reference visit our documentation site

Packages

Articles

Community

Online playground

You can try effector with online playground

Code sharing, Typescript and react supported out of the box. Playground repository

DevTools

Use effector-logger for printing updates to console, displaying current store values with ui or connecting application to familiar redux devtools


More examples in documentation

Learn more

Support us

Your support allows us to improve the developer experience 🧡.

Contributors

Dmitry/
Dmitry
andretshurotshka/
andretshurotshka
Sova/
Sova
Alexander
Alexander Khoroshikh
popuguy/
popuguy
Igor
Igor Kamyşev
pxbuffer/
pxbuffer
Valeriy
Valeriy Kobzar
Yan/
Yan
Ruslan
Ruslan @doasync
Illia
Illia Osmanov
mg901/
mg901
Igor
Igor Ryzhov
Nikita
Nikita Kungurcev
Edward
Edward Gigolaev
Viktor/
Viktor
Arthur
Arthur Irgashev
Ilya/
Ilya
Ainur/
Ainur
Ilya
Ilya Olovyannikov
Mikhail
Mikhail Kireev
Arutyunyan
Arutyunyan Artem
Dmitrij
Dmitrij Shuleshov
Nikita
Nikita Nafranets
Ivan
Ivan Savichev
Aleksandr
Aleksandr Osipov
bakugod/
bakugod
Зухриддин
Зухриддин Камильжанов
Mikhail
Mikhail Krilov
Victor
Victor Didenko
Viktor
Viktor Pasynok
Kirill
Kirill Mironov
Andrei/
Andrei
Denis/
Denis
Filipkin
Filipkin Denis
Ivan/
Ivan
Ivanov
Ivanov Vadim
sergey20x25/
sergey20x25
Rastrapon/
Rastrapon
Dan/
Dan
Bohdan
Bohdan Petrov
Bartłomiej
Bartłomiej Wendt
Andrei
Andrei Antropov
☃︎/
☃︎
xaota/
xaota
cqh/
cqh
Aldiyar
Aldiyar Batyrbekov
Vladimir
Vladimir Ivakin
Vitaly
Vitaly Afonin
Victor/
Victor
Tauyekel
Tauyekel Kunzhol
Ivan/
Ivan
Sozonov/
Sozonov
Samir/
Samir
Renat
Renat Sagdeev
Kirill/
Kirill
Denis
Denis Sikuler
Chshanovskiy
Chshanovskiy Maxim
Arsen-95/
Arsen-95
Anton
Anton Yurovskykh
Anton
Anton Kosykh
Aleksandr
Aleksandr Belov
Usman
Usman Yunusov
Vasili
Vasili Sviridov
Vasili
Vasili Svirydau
Victor
Victor Kolb
Vladislav/
Vladislav
Vladislav
Vladislav Melnikov
Vladislav
Vladislav Botvin
Will
Will Heslam
xxxxue/
xxxxue
The
The Gitter Badger
Simon
Simon Muravev
Shiyan7/
Shiyan7
Sergey
Sergey Belozyorcev
Satya
Satya Rohith
Roman
Roman Paravaev
Roman/
Roman
Robert
Robert Kuzhin
Raman
Raman Aktsisiuk
Rachael
Rachael Dawn
vladthelittleone/
vladthelittleone
Vladimir/
Vladimir
roman/
roman
Eris/
Eris
lightningmq/
lightningmq
Kirill
Kirill Leushkin
kanno/
kanno
Ilya/
Ilya
ilfey/
ilfey
Houston
Houston (Bot)
Grigory
Grigory Zaripov
dmitryplyaskin/
dmitryplyaskin
Stanislav/
Stanislav
Артём
Артём Жолудь
ansunrisein/
ansunrisein
Anatoly
Anatoly Kopyl
Yesset
Yesset Zhussupov
Rasul
Rasul
bigslycat/
bigslycat
Dmitry
Dmitry Dudin
Dmitry/
Dmitry
Denis
Denis Skiba
Dinislam
Dinislam Maushov
Ayu/
Ayu
David/
David
Egor
Egor Gorochkin
Amar
Amar Sood
Александр/
Александр
Alexander/
Alexander
Alex
Alex Anokhin
jokecodes/
jokecodes
Alex
Alex Arro
Aleksandr
Aleksandr Grigorii
Abel
Abel Soares Siqueira
7iomka/
7iomka
Abdukerim
Abdukerim Radjapov
0xflotus/
0xflotus
Pavel
Pavel Hrakovich
Oleh/
Oleh
Oleg/
Oleg
Mike
Mike Cann
Nikita
Nikita Svoyachenko
Marco
Marco Pasqualetti
Ludovic
Ludovic Dem
Leniorko/
Leniorko
Lebedev
Lebedev Konstantin
Joel
Joel Bandi
Jesse
Jesse Jackson
Jan
Jan Keromnes
Ivan/
Ivan
Infant
Infant Frontender
Ilya
Ilya Martynov
Gleb
Gleb Kotovsky
Gabriel
Gabriel Husek
Ed
Ed Prince

Tested with browserstack