It is a JavaScript library – Dan Abramov
It is – but there is a catch…
The Birth 🐣
Redux was created by Dan Abramov (a highly talented engineer) inspired by the Elm architecture. Unlike JS's typically imperative approach, Elm embraces a declarative paradigm, based on intentions and expectations, where data flow follows the pattern:
Cmd → Msg → update → Model
A pseudo code of HTTP request might look like that:
Loading code example...
Here,
getUsers
(an intent) produces a Cmd, which is handled by Elm's effect manager (a middleware). When completed, it produces a Msg UsersGot(payload)
which acts like an action, and is passed to the update (a reducer) function, to determine how to update the model (a state).Notice that the imperative part – how the request is actually made – is moved outside of the core logic, making it easy to swap or mock. This separation makes the code easily testable, because the only thing needed to assert is that the
getUsers
produces a Cmd(UsersGot)
.This pattern resembles interface declarations in OOP or style definitions in CSS, where the implementation (a function body or an animation effect) is decoupled from the environment, where the intent is defined.
This is how the declarative approach is different from the imperative one, and what set the stage for Redux's design, but also its limitations.
The Pitfalls 🕳️
Redux quickly gained well-deserved attention for its innovative approach, addressing several pain points in React applications:
- Offered a structured alternative to chaotic state management
- Aligned closely with React's directive approach
- No more pain struggling with Context API
- No more issues with re-rendering, thanks to connect
- No more extra props thanks to
mapStateToProps
- Powerful DevTools with useful state inspector and a time travel, provided a robust DX
However, the cost of the benefits came with an overwhelming amount of boilerplate code required to support a predictable execution flow. To manage state effectively, Redux introduced a complex set of concepts, significantly raising the learning curve:
- Store – a centralized home for the state
- Reducer – an atomic state update handler
combineReducers
– state tree builder
- Selector – state extractor
- Action (name, payload) – an intention to update the state
- Dispatch – the intention runner
- Middleware – the intention handler
Beyond these, developers also had to contend with:
- React-specific APIs, required to connect Redux, and utilize the state
- Immutability provision using the external dependencies
Over time, these complexities led to a frustrating developer experience:
- Excessive repeatability – it takes time to write, and read through this noise
- Lack of clear guidelines or best practices for organizing state
- Synchronous nature lacking built-in mechanisms for handling side effects
- Integrating 3rd-party libraries often required translating their imperative APIs into Redux's declarative flow, adding further overhead
These pitfalls highlight why Redux's rigid structure, while initially promising, became a burden for developers, suppressing productivity and scalability.
The Ecosystem 🌍
To address Redux's shortcomings, a range of opinionated solutions started pop in, each attempting to mitigate its complexities. However, these tools often introduced their own issues, further highlighting Redux's flawed foundation:
redux-actions
Aimed to reduce boilerplate by simplifying the creation of action objects, which consist of just two properties: type and payload. While it eliminated some repetitive coding, the need for a dedicated library to streamline such a basic task underscores how cumbersome Redux's core design can be.
2.
redux-thunk
Designed to handle asynchronous logic within a single dispatch. It adds to the growing list of concepts: thunks (higher-order functions), and even… a pseudo-service container, blurring the line between state management and inversion-of-control, raising the question: are we still managing state, or are we building an overly intricate system?
3.
redux-saga
An amazing library that, arguably, comes closest to embracing Redux's nature by using generators to manage side effects, demonstrating that declarative effect management is possible. However, its steep learning curve and reliance on an extensive set of concepts – call, put, takeEvery, takeLatest, fork, spawn, join, and more – make it terrifying. Generators, rarely used elsewhere in the JavaScript ecosystem, feel like an awkward fit, adopted only to enforce the Redux's rules. The complexity of adapting to this paradigm outweighs the benefits, especially when simpler alternative like Promises are more intuitive and easier to maintain.
4.
RTK, RTK-Query
An ultimate solution, aimed to reduce the boilerplate, align the DX demands, and figure out the areas of responsibility. This is a whole framework attempted to keep up with modern solutions, however, still built atop Redux's legacy engine, introduced its own complexities: a redundant query-caching API client, new concepts like slices, async thunks, and code generation. Debugging with Redux DevTools became less straightforward, as action names are now often generated at runtime, making it harder to trace them in the codebase.
Each new tool brought additional overhead, making onboarding slower and debugging more cumbersome. Far from solving Redux's core issues, the ecosystem's growth exposed its inability to evolve into a simple, scalable solution.
The End 🏁
It is a JavaScript library – Dan Abramov
…but there's a catch, Dan – it's not less than an architecture, wrapped into a library.
JS is inherently imperative. Developers typically think in terms of sequential instructions rather than a list of intentions. This approach delivers a straightforward and predictable DX that aligns with the language's natural flow.
Redux, however, is not merely a state management library – it's an environment designed for declarative communication, inspired by the Elm architecture. Redux's large ecosystem offers features rather than solutions. These tools, while innovative, have largely failed to provide long-term scalability. Redux, at its core, feels like a niche MVP that gained popularity more by chance than by design.
The Redux experience varies drastically from one app to another. The opinionated "cooking", even without the extensions, makes it nearly impossible to master. This inconsistency creates a frustrating DX, preventing the library from evolution and discouraging widespread adoption of best practices.
Some claim Redux excels in large applications, but where's a single public comparison of different state management solutions applied at least to a really complex task? Without such benchmarks, the claim feels more like wishful thinking than a proven fact.
Redux is a library by fact, but in a real world it's tightly coupled with the Elm architecture, demanding for an unnecessary mental shift, and that's why Redux just can't survive.
The Future 🔮
RSC, SSG, PPR (Next.js 15) reshaping state management by inlining the state during the build time, and eliminating the need to store state outside of components. Tools like React-Query and SWR have taken on the responsibility of managing cached state, which is frequently sufficient for small-mid sized apps.
However, complex apps still require storing computed results in memory, where state management tools continue to play a critical role. My personal preference is Jotai, which creates a seamless pipeline to a data source while carefully preventing unnecessary re-renders, offering a simple and intuitive DX.
To dive deeper, I recommend reading the Thoughts on State Management Libraries in the React Compiler Era by Daishi Kato, the creator of Jotai, Valtio, Waku, and a maintainer of Zustand.