This app is way too simplistic to yield any benefit from state management. This reads more like an intro to redux than a comparison of building apps with vs without redux.
I ask myself this a lot, are all the boiler plating you have to do for Redux “really” necessary just to have state management?
Can we try combine the actions, types, and reducers into a single domain so they aren’t seemingly repeated in 3-4 different locations. Maybe an option to combine or separate as needed.
I know there are alternatives like Mobx and even the newer React Hooks so this seem to affirm my thinking that it might not be all necessary.
And I personally haven’t seen any benefits from all the separations you do in Redux at least from projects I’ve worked on, all it led to was verbosity and more typing.
It tend to make code more prone to mistakes and harder to follow.
If I see the benefits, I wouldn’t mind but either I don’t see it or haven’t seen it yet. It just feels a bit like pre-mature optimization or over-abstraction.
In my experience what ends up happening is that for 85% of application functionality you end up using a library that "manages" the boilerplate for you. For example, is you are writing an application that communicates with a REST API I use something like github.com/klis87/redux-saga-requests - and the store, actions, and reducers are taken care off in just a few lines and I get loading states, collections, and errors in return. For the other 15% you write your own reducers because you need some one-off functionality.
I like this because I get to opt in to widely used and understood state management framework - I don't have to teach other engineers whats going on or how to debug the app, and next I have a tried and tested framework that doesn't get too unwieldily on complex components, like query builders.
However, at the end of the day, the projects I've used Redux were all enterprise-y SaaS web apps with several bells and whistles. I can definitely see Redux being a chore on other applications that don't have a crazy scope.
> It tend to make code more prone to mistakes and harder to follow.
What mistakes are you encountering with Redux that you aren't seeing in a vanilla React app? IMO, redux makes your code uncomplicated and super easy to follow, but you pay for it with making code less portable via the context api/with global state.
+1, this is what we do in many of our tens-of-thousands of LoC codebases where I work and it works out great. I can't think of a single downside since we switched, actually.
We use MobX and I highly recommend it. It basically turns pub/sub into a non-issue, while remaining unopinionated about the plain JS patterns you use to actually manage your data. Using it as intended requires some build configuration, but there's zero boilerplate. It can even be more performant than Redux under certain circumstances: it allows components to update without having to traverse the tree above them first.
Every answer to your post seems to suggest a new JavaScript library to fix this problem. But I have to ask: is another library really the answer when the problem is caused by a library in the first place? Are we supposed to believe that the complexity of Redux is reduced because it's hidden?
The JavaScript community seems intent on repeating all the mistakes the C community made decades ago. Global state? Let's do it!
There's a huge difference between global state in C and redux, though - redux state is immutable and updated atomically. And immutable state really is a game changer in reducing complexity.
When you're just dealing with functions and data, I don't know how it could get more simple. I know that it has made my code much more readable, testable, and organized.
> redux state is immutable and updated atomically.
Immutability certainly makes it less harmful, but it still means that a function in one area of the codebase can modify the global state and affect a completely different area of the codebase.
Atomicity is basically a meaningless buzzword in this situation, given JavaScript doesn't have parallelism.[1]
> When you're just dealing with functions and data
When you're just dealing with functions and data, you aren't using Redux, so really anything you have to say after that isn't relevant.
At the end of the day, you can make all these mistakes yourself if you want, but you'd be better served by listening to the programmers of the past and learning from their mistakes instead.
[1] What you're actually talking about is the fact that the entire transition is executed in the same event, which isn't the same thing as atomicity, but is actually somewhat useful because it works around the horrible things JS programmers often do that make it difficult to tell what order state transitions will be executed in, despite being in a single-threaded environment. However, the fact that it enables inserting state transitions into utterly untraceable code is hardly a point in Redux's favor if you want your code to be traceable in the first place.
One of the core points of Redux is that all the state update logic gets combined into a single reducer function. How you choose to split the reducer logic into multiple files is up to you, so it may be separated in _that_ sense, but it's ultimately all getting pulled into one spot. So no, you don't have random "functions modifying global state and affecting different areas".
And no, it's not "untraceable code". Redux is explicitly meant to make it much easier to trace when, where, why, and how your state has been updated. All state changes happen via reducer functions (not random pieces of code somewhere in your UI logic), and the state updates themselves can be viewed using the Redux DevTools (which can now even give a stack trace for every individual dispatched action).
> and the state updates themselves can be viewed using the Redux DevTools (which can now even give a stack trace for every individual dispatched action).
So now we've added a second tool to fix the problem caused by the first tool.
You don't seem to actually understand how Redux works.
All interactions revolve around "dispatching" an action to the store. The root reducer function is called as part of that `dispatch()` sequence. This gives a central point that allows tracing the resulting state changes:
store.dispatch({type: "INCREMENT"})
The store can be configured on app startup to enable the Redux DevTools browser extension to automatically log all dispatched actions, including showing the action types and viewing the results of each dispatched actions (action contents, resulting state, and state diff vs the previous state).
This isn't "fixing a problem" with Redux, this is a core designed capability to allow you to understand how your app is behaving.
Again, I'm going to ask, where are you calling `store.dispatch({type: 'INCREMENT'})`?
You're saying I can't modify global state and have effects on a completely different area of the code, but I'm saying, yes, you can. All you're doing is telling me about the fairly-pointless layers of indirection Redux adds. Adding a layer of indirection doesn't solve the problem.
With the useReducer hook a lot of these differences have gone away. The important difference is that redux will use global state. It's use of the Context API makes components less portable.
Redux has it's place, I think it's structure can be nice at times, but you pay for it. This app is not anywhere near the size where I would consider using Redux though.
I find where redux shines is when you move past the trivial app (which TODO mvc style apps are). Especially if you're sharing various portions of state between components at different levels and routes.
You have hooks for redux too, the following is at the top of pretty much all my main components.
import { useSelector } from 'react-redux';
import useActions from './ComponentActions';
In my *Actions file, I export a wrapper for the actions individually, which makes using actions pretty straight forward...
I'd probably use saga if I ever needed anything much more complicated, which is rarely the case.
In general, I tend to feel that following a few consistent patterns with Redux tends to let you avoid a lot of problem cases dealing with state compared to other options. Also, I tend to inject an intercept into my root reducer, this way I can force dispatch a starting state for purposes of integrated browser testing (gets deep-merged with current state).
beforeEach(async () => {
base = await page.evaluate(() => {
// set store to a different initial state
__STORE__.dispatch({ type: '__TEST_OVERRIDE__', payload: startingState });
...
return __BASE__; // <-- injected configuration via server
});
...
Every React + Redux I've worked with were needlessly bloated & slow. I think that there is something that leads to bloat and slow performance in the react+redux world, but I can't quite identify it yet.
Bloated and slow are not terms I typically associate with React and Redux... What else is being used in the application(s)?
An app I've been working on even does some simple SVG overlays via React/Redux and responds to mouse events with very little issue. It's probably the biggest example of pain I've felt with it.
Of course, an app can be written very badly depending on how "clever" a developer tries to get, or how it may be using remote resources and how that interacts. Without more detail, hard to tell.... of course there's the matter of publishing without "production" mode being set too.
The app in question does have about a 500kb payload, which is larger than I'd like... but using material-ui and a few complex features it's not nearly as bad as many other apps I've used.
I have had exactly the opposite experience. I recently rolled onto a React project without Redux and the code was pretty gross. Multi hundred line React components with markup, API calls, and business logic all mixed together in an untested and untestable cocktail garnished with a heavy dose of prop drilling and deeply nested callbacks.
Adding Redux to this app was a huge improvement. Test coverage is now almost 100%. The views are now tiny and simply render props and dispatch actions. Complex workflows are handled beautifully by Redux sagas. Overall, the codebase is now one of the cleanest I have ever worked on.
I am not saying you have to have Redux to have a good frontend codebase, but it definetly helps a lot. If your app is even remotely complex, its hugley helpful to have a global data store. If you are going to have workflows with asynchronous components, Redux sagas are an incredible tool.
My hunch is that the apps that don't use Redux either 1. suck or 2. have had some smart person hand roll a framework that has most of the core principles of Redux in it. I am not that smart, and I don't want my codebase to suck, so Redux is the best choice for me.
Have you used redux-sagas on any projects before this one? How do you feel it compares with redux-thunk?
I've used redux-thunk for all of my past projects but recently inherited a project using sagas. It seems to be pretty unorganized and I'm wondering how much effort I should put into cleaning it up, if it's even possible.
Have you tried thunks using async/await? IMO its all the benefits of being easy to read and understand without having to learn a totally new concept (generators).
Yep, agreed. The biggest missing capability there is being able to _respond_ to dispatched actions, which thunks can't do. But, most apps don't immediately need to do that anyway.
Sagas are an incredibly powerful tool... and 95% of Redux apps don't need them. Most apps should stick with thunks until they truly have a need for very complex async logic.
Doesn't ring true to my experience at all. I recently polished off a ~50k LoC application that runs blazingly fast even on IE10.
My experience is when people complain about slowness it's usually because the application architecture/design is poor and not because of any particular technical choice.
I don't think there's anything about React + Redux that is inherently slow, but I don't think it's as "idiot proof" as other frameworks out there. One app at my previous employer cargo-culted redux to the point where no component was allowed to have its own state. With its massive global state, every keystroke was painfully slow (thanks, redux-form).
I've seen a couple of apps that have the same anti-pattern. They have a connected component that is basically a global container of all other components. They don't do anything sophisticated with shouldComponentUpdate, so whenever something changes all child components (i.e. basically the whole app) is rendered again.
On its own that isn't necessarily that bad, but combine that with form fields directly manipulating the Redux state (i.e. an action for every keypress) and it's easy to build an app that has poor performance.
I've been rewriting one of my hobby projects. I'm going from vanilla javascript to react as a newbie in react. The project isn't even close to being finished when I started the task of rewriting a month ago and the main thing I've noticed is improvement in code structure.
I think someone would have a better time getting their head around the code now and without me guiding them than before. Although the project was already well organized. I make directory structure and naming conventions a high priority/OCD obsession. I think thats mainly why my hobby projects never see an end.
Unlike the author I haven't went with Redux and I've been just using react context with hooks for passing data. I'm really pleased with react context because now things are even more organized than without using context. The only downside with react that I've encountered is trying for the least amount of re-rendering when context changes. I've been reading a lot of optimizing react blogs online. I'm trying to make the app realtime focused with websockets and I don't want to face a difficult situation later on where many users blows up the app to a crawl. Anyway I just wanted to express context is really useful so far.
This article isn't really a comparison. It's almost completely dedicated to explaining Redux. In fact the complexity of explaining even this simple app with just a couple of components, should serve as a reminder of how complex Redux is. The only real mention of the plain ReactJS version is near the end, where it actually links to another article which compares ReactJS and Vue.
I personally think that redux is very simple when you just explain its key idea.
It only becomes complex when people trying to show a "cultured" way to write a large redux/react app bring in a ton of containers, controllers, action classes with methods for various auxiliary conveniences, etc. Even the example in redux's own docs is unnecessarily complex.
This is not how you explain things.
For an explanation, i'd use a single file, with 2-3 actions, using strings (or an enum) to denote actions, with a reducer right next to renderer, and a single-function react component.
Then the data flow becomes apparent, and the usefulness of redux for keeping state within the unidirectional loop becomes also apparent.
After getting the key idea, the reader will have an easy time understanding tools built around it, ways to fold up the boilerplate, etc. A react/redux app can be very compact and approachable, even when the logic is complex.
Yep, this is something I plan to do in our upcoming docs revamp. The initial tutorials will drop action creators, drop `const ADD_TODO = "ADD_TODO"`, drop splitting things into multiple files, etc.
And yeah... a lot of the stuff normally associated with Redux is _intended_ as a "large project maintainability best practice" kind of thing.
Mark, your writings have really helped me to understand the redux landscape (inasmuch as I do), so I'd be curious to know what you think of this, if you've seen it: https://www.youtube.com/watch?v=JmnsEvoy-gY ("Good Action Hygiene with NgRx", Mike Ryan, NGConf April 2018)
The context is ngrx but the territory is familiar. Two things struck me in particular: one (the key point of the talk) is the idea that actions should be named as effects not intentions. The other (which I loved) is the idea of having action types that look like (say) "[Todo List Page] Add Todo Clicked" rather than "ADD_TODO" — it certainly makes the history more legible in redux-devtools. (I'm unsure how to name my consts then, though; I'm going with TODO_LIST_PAGE_ADD_TODO_CLICKED but it's very verbose; in the video it looks like he's not bothering with consts at all, and is just using the strings directly at dispatch and reduce time, and maybe there's something in that — the strings are specific enough that there'd be no scope for confusion, I guess.)
Just wondered what you thought of all this? I've read your idiomatic redux series, but maybe your thoughts have moved on since then...
I have that talk bookmarked to watch, but my bookmarks queue is like 600+ deep at this point :)
Just going by your summary:
Yeah, I do find it interesting how some different parts of the community have chosen to format their action strings differently. Redux itself literally doesn't care, of course - the only requirement is that `action.type` not be undefined.
As I said in "The Tao of Redux, Part 1: Implementation and Intent" [0], one of the main intents behind Redux is that the action log should be a literally readable history of what's going on in your app with semantic meaning behind each name. You _could_ have a single `"SET_DATA"` action, but that doesn't tell me anything about what's going on.
The Redux docs originally went with the SCREAMING_SNAKE_CASE string format just because that's the common programming world convention for constant values. The naming in the docs also tends towards matching how you would name the action creator, ie, `"TOGGLE_TODO"` -> `toggleTodo()`. Those are perfectly legitimate choices to make, but definitely not the only possible ways to do it.
I've seen plenty of other variations over the years, usually in the form of some "domain" string + the "event" name. For Redux Starter Kit, we've gone with a '/'-separated string, such as `"todos/addTodo"`. The end user is still supplying both parts (via the slice `name` field and the user-defined reducer function name). So, the Angular-ish convention of `"[Some Domain] Some Event"` is perfectly fine, and very readable.
Either way, it doesn't matter what you actually name the _variable_ that points to that action type.
One of the most interesting variations I've seen on this is Slack, which apparently uses entire _sentences_ as their action types, each including a unique numeric ID [1].
Finally, yes, we would generally recommend treating actions semantically as "events in the app", not "setters at a distance". Again, the Redux store itself doesn't care, but the "events" mental modeling promotes dispatching fewer total actions and having multiple reducers independently responding to a given action [2]. We'll emphasize this more in the upcoming docs revamp.
Glad to know my writing has helped! Lemme know if you've got any other questions.
Hey Mark, thanks for taking the time to reply. I hear you on the bookmarks queue! :-)
All makes sense. I hadn't heard of RSK until I read this comment, but I've now seen your other comment in this thread which mentions it[0], watched the "State of Redux" talk referenced there and I'm excited to try it out. Cheers!
For me the problem with Redux is not that it’s complicated, it’s that it’s wildly verbose and adds a huge layer of misdirection to an app.
Some apps are complex enough that they need this. In my experience, a lot of Redux apps really aren’t all that complicated, and people have used Redux because it’s The Thing To Do.
I think that's the problem with demonstrating Redux, no?
You start reaping the benefits when you need a state machine to manage complexity of your application. Thus, to demonstrate Redux's usefulness you need sufficiently complex examples.
That said, the explanation is as easy as saying Redux enables your frontend to be a state machine.
Reducers are very useful and simple to understand, and are great even on smaller apps. It's possible to see that in things like useReducer.
IMO the problem with Redux is that you "need" all the boilerplate around it. I say "need" in quotes because you can easily get around it and not lose much (if anything). And if you do, you start to reap the benefits even in very small apps.
Part of why I only find React+Redux tolerable with TypeScript. Too much bouncing around, too many files open, otherwise. And that's before you layer any other crap on top of that (Saga and such). TypeScript makes it feel as simple as it actually is.
Note that Redux Starter Kit is built in TypeScript itself, and `createSlice` makes it easy to write all the logic for a given slice in a single file. Then, combine that with our new React-Redux hooks API, and it really gets easy to work with Redux in your components:
> Even the example in redux's own docs is unnecessarily complex.
Well here's the issue, because if the project docs are unnecessarily complex then what hope does anyone have of learning the correct way? Come to HN and read your comment?
No–the official documentation needs to speak for itself and be judged on its own merits. If you feel so strongly about how Redux should be taught, you can always send them a PR rearranging the docs and stay on them until it gets merged :-)
> Well here's the issue, because if the project docs are unnecessarily complex then what hope does anyone have of learning the correct way?
The same way one learns about anything the correct way; the Tao that can be told is not the true Tao.
> No–the official documentation needs to speak for itself and be judged on its own merits.
Sure the documentation needs to stand on its own merits, as does he code itself. That's different than judging the code by the (de)merits of the documentation though.
“Redux is too complex” and “Redux has less-than-ideal documentation from an introductory perspective” are both potential problems, but they are distinct problems.
Just to be clear, I wasn't saying Redux has such a problem with it's docs, just that GPs suggestion that it did was different than identifying a problem with the complexity of the software.
We're already planning to revamp the docs in the _very_ near future (like, hopefully starting in the next couple weeks once I get Redux Starter Kit 1.0 out the door).
Redux isn't complex. It's a philosophy on how to manage state, and a few simple concepts to realize that philosophy (store, reducer, action, ...). It has nothing to do with React. Articles which effectively explain Redux by wading through the minutiae of a boilerplate app are anathema to that goal.
If anyone reading this wants to learn Redux, just read the getting started guide, or watch the co-creator's video series.[1] It's wonderful. If anyone just wants a TL;DW, here's mine:
1. The state of your application should be a tree. This tree is immutable. We call it a store.
2. Each branch of your state tree is generated by a pure function of the previous state and an action. We call these reducers.
3. The only way to effect change in your state is to dispatch actions. Dispatching action X triggers the root reducer for action X (which triggers the child reducers with action X, etc.), giving you the next state.
4. Subscribe to store changes with listeners.
That's really it. A general roadmap for managing state in your software. Has nothing to do with React. Or with user interfaces. Just a general approach to software state. You can roll your own in a few minutes for your use-case. If your use case happens to be a React UI or C# app, then yeah, reach for React-Redux or Redux.NET respectively.
But please, please don't learn Redux initially by looking at a React app that uses React-Redux. That's hardcore jumping the gun.
Yes, Redux is easy. The problem is the incredible amount of boilerplate and repeated code in most tutorials and beginner projects, in the form of actions, switch statements and ALL_CAPS_STUFF.
That boilerplate can easily take 10x more lines than "real" code in reducers, even in complex applications. Requiring three different files for each reducer, as in the article presented, is completely overkill, and it only makes sense when you're getting paid by the line of code.
However, there are great solutions to avoid that. I'll echo the recommendation of createSlice:
> Requiring three different files for each reducer, as in the article presented, is completely overkill
He only has one [0]. Note that the parent directory is "redux", not "reducer" - the actions and actionTypes are separate from the reducers, because there isn't a 1:1 relationship. This separation is because you could, for example, have a second reducer that listens to the same Add action as the one in this example - it doesn't need it's own action or actionType.
I'll disagree with you on that last point. We specifically encourage that pattern - treating actions as "events" that any part of the reducer logic can respond to independently, rather than "setters".
Now, it's certainly true that _most_ actions will only be handled by a single slice reducer, but having many slice reducers responding to the same action is an incredibly useful pattern:
I do get the point of it being this way, but my problem with it is that it feels too much like magic, I think I just prefer to have the 1:1 relationship, like I already do with javascript functions or HTTP calls.
But even leaving that aside, I still think that this specific "feature" is not worth the amount of boilerplate you need in most Redux apps, so it's not a nice tradeoff IMO :(
I worked in some bigger apps, and before adopting ducks (and later a homebrew version of createSlice) we had some bugs caused by multiple reducers firing.
I also think that it makes Flux/Redux a bit harder to explain
That's all correct, but in common usage 'Redux' almost always means 'React-Redux', i.e. the integration of Redux into a React app. And given the OP, that should be clear in the context of my comment. And since we are talking about React-Redux, it is correct to say it is complex–as can plainly be seen in the OP.
I simplify 3 even further when introducing it to co-workers: Dispatching an action runs it on every reducer, which is why "type" is used it filter out the relevant ones.
If anyone here is familiar with the game factorio ... not using redux feels like conveior belts vs redux being flying robots. Suddenly everything seems easy and accessible. And writing spaghetti is nearly impossible.
If you ever intend to hire another person into a mature app using redux, he/she is gonna love you for presenting all state via dev toolbar on a silver platter.
Hi, folks. I'm a Redux maintainer. Wanted to let everyone know about the two major things we're working on at this point.
First, we have a new official package called Redux Starter Kit [0], which is our recommended toolset for writing Redux apps with less boilerplate. It includes utilities to simplify several common Redux use cases, including store setup, defining reducers, immutable update logic, and even creating entire "slices" of state at once without writing _any_ action creators or action types by hand.
RSK is useful for both folks who are new to Redux and experienced users, and can be used day 1 in a new project or incrementally added to an existing project. I'd strongly encourage anyone who's using Redux to try it out. In particular, I'd suggest reading through the "Advanced Tutorial" page [1], which demonstrates how to use RSK with TypeScript, thunks for async data fetching, and our new React-Redux hooks API. You might also want to read through the issue comment that lays out my "Vision for Redux Starter Kit" and the problems it's meant to solve [2]
I just published RSK v0.8 this evening [3] which has a couple small breaking changes (primarily renaming a `slice` field to `name` and making it required). After that, I'm hoping to nail down a couple last bits of configuration, and then push it to 1.0 within the next week or two.
After that, we're going to start working on a major revamp of the Redux core docs. You can see my planned outline here [4]. The goals for the docs revamp include:
- Updating the tutorials, including removing outdated concepts like references to "Flux", distracting mentions of terms or warnings that beginners don't need to worry about, adding diagrams, and finding ways to improve the explanations (like changing the "Middleware" page to explain how to _use_ middleware, rather than why they work this way). We will also add a tutorial section that shows how to use Redux Starter Kit, and recommend that people use it as the "default way to use Redux".
- Adding a "Real World Usage" docs section. This would include topics like choosing async middleware, debugging, folder structures, performance, and so on.
- Adding a "Style Guide" page. The Redux docs are deliberately unopinionated about things like folder structures, async middleware, structuring actions and reducers, and so on. However, we'd like to provide some official guidance on our current recommended best practices. The Vue docs have a great page with their recommendations, why they recommend certain patterns, and how strongly they recommend them, and we'd like to do the same thing here. For example, we'd recommend using a "feature folder" or "ducks" pattern for structuring files, treating actions as "events" instead of "setters", and trying to put as much logic as possible in reducers.
- Adding a "Thinking in Redux" section. This would include concepts like mentally modeling actions, the history of Redux and its inspirations, how it's meant to be used, and why it works the way it does.
I'd love to have additional help from the community in working on this docs revamp - I certainly can't do it all myself :)
Finally, I'd encourage folks to watch my Reactathon 2019 talk on "The State of Redux" [5], where I talked about Redux's market share and how it compares to other tools, as well as future directions for the library. In addition, my post "Redux - Not Dead Yet!" [6] addresses some common questions about how things like hooks and GraphQL relate to Redux.
React and it's cousin in tow, Redux put food on my table. But like a famous HN comment said: how many startups have been burn out due to the complexity of wiring up those two powerful tools. I think frankly, when comparing frontend frameworks the benchmarks should be 1. lines of code, since that determines delivery 2. ease of maintenance as that enables easy to add new features 3. how fast the app executes on the customers device. And frankly React + Redux results in writing a lot of code
I agree, but with CRA + JSX + React Hooks usercode is a lot terser. I tried React around 2014-2015 and hated it, now I really like it. Besides declarative programming, styled components has been a life changer for the better.
I'm still torn with Redux though, seems great for big projects/teams, but total overkill for small ones. I wrote a small alternative for my personal projects (https://github.com/franciscop/statux), what do you think about how it'd fare with your points? Looking for improvements, and I saw a bug once (though I haven't been able to reproduce and not 100% sure it was within the lib).
This is super true, React/Redux with hooks is really nice to work with. If you use the redux hook it is super apparent what is coming from store, what is coming from external props and what is internal state.
It makes it dead easy to cold read a components functionality and super simple to detect code smells.
How many startups burnt out writing Backbone and jQuery apps? React and Redux are solving a flaw with design architecture, in addition to performance improvements, like managing the DOM updates for you based on state. Startups aren't burning out because of these technologies, which immensely help building large applications.
1. Redundancy is not necessarily bad. Just learn to recognize the balance (that’s where seniority comes in) 2. LOC is a pretty outdated measure of productivity (forget about estimations of deliverability). It’s fun to watch tge stats but in no way LOC stats can tell you about productivity, performance, or reliability.
Essentially redux is programming the state part of your app outside your UI and having an immutable approach where given a state X with Y action, Z will always result so its good for testing. This does allow you to reuse the redux parts in different frameworks quite easily but I have never done that myself. I personally find redux to be heavy for most apps as most apps are small but in teams redux shines.
A while back I was building a large application for an internal team at my company. It got to the point where local state was just not solving all of my problems so I started to explore Redux. I had been creating React apps for several years but I could not wrap my head around Redux - it was far too much complexity adding all sorts of actions, reducers, boilerplate, etc. I started looking for alternatives and stumbled upon Mobx which I liked much better and have been using ever since.
Mobx-react[0] felt like much less work with virtually the same outcome. You create a single (or multiple stores), pass it into your app with a
<Provider Store={store}>
<App />
</Provider>
and you are off to the races. When adding the global state to a component, it was super easy:
export default inject('Store')(observer(MyComponent))
Or use decorators (requires ejecting create-react-app or modifying the webpack configuration settings):
@inject('Store')
@observer
class MyComponent extends Component {...}
export default MyComponent
Accessing the store:
const {myValues} = this.props.Store
In the store you organize your code by observables (global variables), actions (functions that modify observables) or computed (highly optimized functions that return values derived from state). I treat it as a "source of truth" in a single file.
import {observable, action, computed, decorate} from "mobx"
class Store {
// Observables
myValues = [0, 1, 2, 3, 4]
// Actions
addValue = num => myValues.push(num)
// Computed
get getTotal() {
return myValues.reduce((total, cur) => total + cur)
}
}
decorate(Store, {
myValues: observable,
addValue: action,
getTotal: computed
})
const store = new Store()
export default store
In the end I felt that my code was far more readable and maintainable but your miles may vary.
I can attest to the genius that is MobX. Having worked on a frontend project built with RxJs + React for nearly two years, I feel like I have kept all of the benefits of reactivity and discarded a lot of complexity and learning curve that mostly came from RxJs.
The API surface of MobX is a breeze, and it kind of pushes you into the pit of success.
Redux isn't complex. It provides a simple way of doing state management. React-redux is slightly more complex, but only because the docs give the horrible advice of grouping things by type instead of domain. Using the "ducks" pattern is vastly superior and simplifies a lot. It also hurts, I think, that most people create actions and reducers that are 1:1. Those should be different things.
If you find Redux "Too complex" or you feel "it requires too much boilerplate" then this is a clear indication you don't need Redux. The real use-case for Redux is highly complex applications, if you're using redux for your todo app then of course the cost/benefit ratio for you is not gonna work out
82 comments
[ 46.4 ms ] story [ 1911 ms ] thread>But wait, where is the app that just used React? I thought this was a comparison piece?
>This article is really a beginner’s guide to Redux to demonstrate how you would go about adding Redux into an existing React app.
Can we try combine the actions, types, and reducers into a single domain so they aren’t seemingly repeated in 3-4 different locations. Maybe an option to combine or separate as needed.
I know there are alternatives like Mobx and even the newer React Hooks so this seem to affirm my thinking that it might not be all necessary.
And I personally haven’t seen any benefits from all the separations you do in Redux at least from projects I’ve worked on, all it led to was verbosity and more typing. It tend to make code more prone to mistakes and harder to follow.
If I see the benefits, I wouldn’t mind but either I don’t see it or haven’t seen it yet. It just feels a bit like pre-mature optimization or over-abstraction.
I like this because I get to opt in to widely used and understood state management framework - I don't have to teach other engineers whats going on or how to debug the app, and next I have a tried and tested framework that doesn't get too unwieldily on complex components, like query builders.
However, at the end of the day, the projects I've used Redux were all enterprise-y SaaS web apps with several bells and whistles. I can definitely see Redux being a chore on other applications that don't have a crazy scope.
What mistakes are you encountering with Redux that you aren't seeing in a vanilla React app? IMO, redux makes your code uncomplicated and super easy to follow, but you pay for it with making code less portable via the context api/with global state.
Can I ask what other state management libraries you have used? If Redux is super easy, how do you feel about MobX or even VueX.
You can actually take this pretty far with regard to React in general. You can get in to some pretty funky situations with "proper" React boilerplate.
This is what the "duck" pattern is for: https://github.com/erikras/ducks-modular-redux
https://redux-starter-kit.js.org/api/createSlice
https://redux-starter-kit.js.org/usage/usage-guide
Looking forward to seeing more improvements in this area.
The JavaScript community seems intent on repeating all the mistakes the C community made decades ago. Global state? Let's do it!
When you're just dealing with functions and data, I don't know how it could get more simple. I know that it has made my code much more readable, testable, and organized.
Immutability certainly makes it less harmful, but it still means that a function in one area of the codebase can modify the global state and affect a completely different area of the codebase.
Atomicity is basically a meaningless buzzword in this situation, given JavaScript doesn't have parallelism.[1]
> When you're just dealing with functions and data
When you're just dealing with functions and data, you aren't using Redux, so really anything you have to say after that isn't relevant.
At the end of the day, you can make all these mistakes yourself if you want, but you'd be better served by listening to the programmers of the past and learning from their mistakes instead.
[1] What you're actually talking about is the fact that the entire transition is executed in the same event, which isn't the same thing as atomicity, but is actually somewhat useful because it works around the horrible things JS programmers often do that make it difficult to tell what order state transitions will be executed in, despite being in a single-threaded environment. However, the fact that it enables inserting state transitions into utterly untraceable code is hardly a point in Redux's favor if you want your code to be traceable in the first place.
And no, it's not "untraceable code". Redux is explicitly meant to make it much easier to trace when, where, why, and how your state has been updated. All state changes happen via reducer functions (not random pieces of code somewhere in your UI logic), and the state updates themselves can be viewed using the Redux DevTools (which can now even give a stack trace for every individual dispatched action).
Which are called where?
> and the state updates themselves can be viewed using the Redux DevTools (which can now even give a stack trace for every individual dispatched action).
So now we've added a second tool to fix the problem caused by the first tool.
All interactions revolve around "dispatching" an action to the store. The root reducer function is called as part of that `dispatch()` sequence. This gives a central point that allows tracing the resulting state changes:
The store can be configured on app startup to enable the Redux DevTools browser extension to automatically log all dispatched actions, including showing the action types and viewing the results of each dispatched actions (action contents, resulting state, and state diff vs the previous state).This isn't "fixing a problem" with Redux, this is a core designed capability to allow you to understand how your app is behaving.
You're saying I can't modify global state and have effects on a completely different area of the code, but I'm saying, yes, you can. All you're doing is telling me about the fairly-pointless layers of indirection Redux adds. Adding a layer of indirection doesn't solve the problem.
Redux has it's place, I think it's structure can be nice at times, but you pay for it. This app is not anywhere near the size where I would consider using Redux though.
You have hooks for redux too, the following is at the top of pretty much all my main components.
In my *Actions file, I export a wrapper for the actions individually, which makes using actions pretty straight forward... I also rely pretty heavily on redux-thunk, which lets me do simple workflows for async... I'd probably use saga if I ever needed anything much more complicated, which is rarely the case.In general, I tend to feel that following a few consistent patterns with Redux tends to let you avoid a lot of problem cases dealing with state compared to other options. Also, I tend to inject an intercept into my root reducer, this way I can force dispatch a starting state for purposes of integrated browser testing (gets deep-merged with current state).
An app I've been working on even does some simple SVG overlays via React/Redux and responds to mouse events with very little issue. It's probably the biggest example of pain I've felt with it.
Of course, an app can be written very badly depending on how "clever" a developer tries to get, or how it may be using remote resources and how that interacts. Without more detail, hard to tell.... of course there's the matter of publishing without "production" mode being set too.
The app in question does have about a 500kb payload, which is larger than I'd like... but using material-ui and a few complex features it's not nearly as bad as many other apps I've used.
Adding Redux to this app was a huge improvement. Test coverage is now almost 100%. The views are now tiny and simply render props and dispatch actions. Complex workflows are handled beautifully by Redux sagas. Overall, the codebase is now one of the cleanest I have ever worked on.
I am not saying you have to have Redux to have a good frontend codebase, but it definetly helps a lot. If your app is even remotely complex, its hugley helpful to have a global data store. If you are going to have workflows with asynchronous components, Redux sagas are an incredible tool.
My hunch is that the apps that don't use Redux either 1. suck or 2. have had some smart person hand roll a framework that has most of the core principles of Redux in it. I am not that smart, and I don't want my codebase to suck, so Redux is the best choice for me.
I've used redux-thunk for all of my past projects but recently inherited a project using sagas. It seems to be pretty unorganized and I'm wondering how much effort I should put into cleaning it up, if it's even possible.
See this Redux FAQ entry for more details:
https://redux.js.org/faq/actions#what-async-middleware-shoul...
My experience is when people complain about slowness it's usually because the application architecture/design is poor and not because of any particular technical choice.
https://redux.js.org/faq/organizing-state#do-i-have-to-put-a...
https://redux.js.org/faq/organizing-state#should-i-put-form-...
On its own that isn't necessarily that bad, but combine that with form fields directly manipulating the Redux state (i.e. an action for every keypress) and it's easy to build an app that has poor performance.
I will agree that it's too easy for components to watch too much of the state, but it's not necessarily wrong to have all the state in one tree.
I think someone would have a better time getting their head around the code now and without me guiding them than before. Although the project was already well organized. I make directory structure and naming conventions a high priority/OCD obsession. I think thats mainly why my hobby projects never see an end.
Unlike the author I haven't went with Redux and I've been just using react context with hooks for passing data. I'm really pleased with react context because now things are even more organized than without using context. The only downside with react that I've encountered is trying for the least amount of re-rendering when context changes. I've been reading a lot of optimizing react blogs online. I'm trying to make the app realtime focused with websockets and I don't want to face a difficult situation later on where many users blows up the app to a crawl. Anyway I just wanted to express context is really useful so far.
It only becomes complex when people trying to show a "cultured" way to write a large redux/react app bring in a ton of containers, controllers, action classes with methods for various auxiliary conveniences, etc. Even the example in redux's own docs is unnecessarily complex.
This is not how you explain things.
For an explanation, i'd use a single file, with 2-3 actions, using strings (or an enum) to denote actions, with a reducer right next to renderer, and a single-function react component.
Then the data flow becomes apparent, and the usefulness of redux for keeping state within the unidirectional loop becomes also apparent.
After getting the key idea, the reader will have an easy time understanding tools built around it, ways to fold up the boilerplate, etc. A react/redux app can be very compact and approachable, even when the logic is complex.
And yeah... a lot of the stuff normally associated with Redux is _intended_ as a "large project maintainability best practice" kind of thing.
The context is ngrx but the territory is familiar. Two things struck me in particular: one (the key point of the talk) is the idea that actions should be named as effects not intentions. The other (which I loved) is the idea of having action types that look like (say) "[Todo List Page] Add Todo Clicked" rather than "ADD_TODO" — it certainly makes the history more legible in redux-devtools. (I'm unsure how to name my consts then, though; I'm going with TODO_LIST_PAGE_ADD_TODO_CLICKED but it's very verbose; in the video it looks like he's not bothering with consts at all, and is just using the strings directly at dispatch and reduce time, and maybe there's something in that — the strings are specific enough that there'd be no scope for confusion, I guess.)
Just wondered what you thought of all this? I've read your idiomatic redux series, but maybe your thoughts have moved on since then...
Just going by your summary:
Yeah, I do find it interesting how some different parts of the community have chosen to format their action strings differently. Redux itself literally doesn't care, of course - the only requirement is that `action.type` not be undefined.
As I said in "The Tao of Redux, Part 1: Implementation and Intent" [0], one of the main intents behind Redux is that the action log should be a literally readable history of what's going on in your app with semantic meaning behind each name. You _could_ have a single `"SET_DATA"` action, but that doesn't tell me anything about what's going on.
The Redux docs originally went with the SCREAMING_SNAKE_CASE string format just because that's the common programming world convention for constant values. The naming in the docs also tends towards matching how you would name the action creator, ie, `"TOGGLE_TODO"` -> `toggleTodo()`. Those are perfectly legitimate choices to make, but definitely not the only possible ways to do it.
I've seen plenty of other variations over the years, usually in the form of some "domain" string + the "event" name. For Redux Starter Kit, we've gone with a '/'-separated string, such as `"todos/addTodo"`. The end user is still supplying both parts (via the slice `name` field and the user-defined reducer function name). So, the Angular-ish convention of `"[Some Domain] Some Event"` is perfectly fine, and very readable.
Either way, it doesn't matter what you actually name the _variable_ that points to that action type.
One of the most interesting variations I've seen on this is Slack, which apparently uses entire _sentences_ as their action types, each including a unique numeric ID [1].
Finally, yes, we would generally recommend treating actions semantically as "events in the app", not "setters at a distance". Again, the Redux store itself doesn't care, but the "events" mental modeling promotes dispatching fewer total actions and having multiple reducers independently responding to a given action [2]. We'll emphasize this more in the upcoming docs revamp.
Glad to know my writing has helped! Lemme know if you've got any other questions.
[0] https://blog.isquaredsoftware.com/2017/05/idiomatic-redux-ta...
[1] https://twitter.com/erikras/status/1153350835738468363
[2] https://redux.js.org/faq/actions#is-there-always-a-one-to-on...
All makes sense. I hadn't heard of RSK until I read this comment, but I've now seen your other comment in this thread which mentions it[0], watched the "State of Redux" talk referenced there and I'm excited to try it out. Cheers!
[0] https://news.ycombinator.com/item?id=21254525
Some apps are complex enough that they need this. In my experience, a lot of Redux apps really aren’t all that complicated, and people have used Redux because it’s The Thing To Do.
You start reaping the benefits when you need a state machine to manage complexity of your application. Thus, to demonstrate Redux's usefulness you need sufficiently complex examples.
That said, the explanation is as easy as saying Redux enables your frontend to be a state machine.
Reducers are very useful and simple to understand, and are great even on smaller apps. It's possible to see that in things like useReducer.
IMO the problem with Redux is that you "need" all the boilerplate around it. I say "need" in quotes because you can easily get around it and not lose much (if anything). And if you do, you start to reap the benefits even in very small apps.
https://redux-starter-kit.js.org/tutorials/advanced-tutorial
Well here's the issue, because if the project docs are unnecessarily complex then what hope does anyone have of learning the correct way? Come to HN and read your comment?
No–the official documentation needs to speak for itself and be judged on its own merits. If you feel so strongly about how Redux should be taught, you can always send them a PR rearranging the docs and stay on them until it gets merged :-)
The same way one learns about anything the correct way; the Tao that can be told is not the true Tao.
> No–the official documentation needs to speak for itself and be judged on its own merits.
Sure the documentation needs to stand on its own merits, as does he code itself. That's different than judging the code by the (de)merits of the documentation though.
“Redux is too complex” and “Redux has less-than-ideal documentation from an introductory perspective” are both potential problems, but they are distinct problems.
See my proposed changes here:
https://github.com/reduxjs/redux/issues/3313#issuecomment-45...
If anyone reading this wants to learn Redux, just read the getting started guide, or watch the co-creator's video series.[1] It's wonderful. If anyone just wants a TL;DW, here's mine:
1. The state of your application should be a tree. This tree is immutable. We call it a store.
2. Each branch of your state tree is generated by a pure function of the previous state and an action. We call these reducers.
3. The only way to effect change in your state is to dispatch actions. Dispatching action X triggers the root reducer for action X (which triggers the child reducers with action X, etc.), giving you the next state.
4. Subscribe to store changes with listeners.
That's really it. A general roadmap for managing state in your software. Has nothing to do with React. Or with user interfaces. Just a general approach to software state. You can roll your own in a few minutes for your use-case. If your use case happens to be a React UI or C# app, then yeah, reach for React-Redux or Redux.NET respectively.
But please, please don't learn Redux initially by looking at a React app that uses React-Redux. That's hardcore jumping the gun.
[1] https://egghead.io/lessons/react-redux-the-single-immutable-...
That boilerplate can easily take 10x more lines than "real" code in reducers, even in complex applications. Requiring three different files for each reducer, as in the article presented, is completely overkill, and it only makes sense when you're getting paid by the line of code.
However, there are great solutions to avoid that. I'll echo the recommendation of createSlice:
https://redux-starter-kit.js.org/api/createslice
He only has one [0]. Note that the parent directory is "redux", not "reducer" - the actions and actionTypes are separate from the reducers, because there isn't a 1:1 relationship. This separation is because you could, for example, have a second reducer that listens to the same Add action as the one in this example - it doesn't need it's own action or actionType.
[0] https://miro.medium.com/max/1383/1*JsqDyA2Wy_08GnHS6SMDmw.pn...
Actions and ActionTypes are pure noise.
And idk, having multiple reducers responding to the same action is a recipe for trouble.
Now, it's certainly true that _most_ actions will only be handled by a single slice reducer, but having many slice reducers responding to the same action is an incredibly useful pattern:
https://redux.js.org/faq/actions#is-there-always-a-one-to-on...
But even leaving that aside, I still think that this specific "feature" is not worth the amount of boilerplate you need in most Redux apps, so it's not a nice tradeoff IMO :(
I worked in some bigger apps, and before adopting ducks (and later a homebrew version of createSlice) we had some bugs caused by multiple reducers firing.
I also think that it makes Flux/Redux a bit harder to explain
Redux isn't complex, it's complicated - needlessly so.
It takes just one look at its competitors to see this.
If you ever intend to hire another person into a mature app using redux, he/she is gonna love you for presenting all state via dev toolbar on a silver platter.
First, we have a new official package called Redux Starter Kit [0], which is our recommended toolset for writing Redux apps with less boilerplate. It includes utilities to simplify several common Redux use cases, including store setup, defining reducers, immutable update logic, and even creating entire "slices" of state at once without writing _any_ action creators or action types by hand.
RSK is useful for both folks who are new to Redux and experienced users, and can be used day 1 in a new project or incrementally added to an existing project. I'd strongly encourage anyone who's using Redux to try it out. In particular, I'd suggest reading through the "Advanced Tutorial" page [1], which demonstrates how to use RSK with TypeScript, thunks for async data fetching, and our new React-Redux hooks API. You might also want to read through the issue comment that lays out my "Vision for Redux Starter Kit" and the problems it's meant to solve [2]
I just published RSK v0.8 this evening [3] which has a couple small breaking changes (primarily renaming a `slice` field to `name` and making it required). After that, I'm hoping to nail down a couple last bits of configuration, and then push it to 1.0 within the next week or two.
After that, we're going to start working on a major revamp of the Redux core docs. You can see my planned outline here [4]. The goals for the docs revamp include:
- Updating the tutorials, including removing outdated concepts like references to "Flux", distracting mentions of terms or warnings that beginners don't need to worry about, adding diagrams, and finding ways to improve the explanations (like changing the "Middleware" page to explain how to _use_ middleware, rather than why they work this way). We will also add a tutorial section that shows how to use Redux Starter Kit, and recommend that people use it as the "default way to use Redux".
- Adding a "Real World Usage" docs section. This would include topics like choosing async middleware, debugging, folder structures, performance, and so on.
- Adding a "Style Guide" page. The Redux docs are deliberately unopinionated about things like folder structures, async middleware, structuring actions and reducers, and so on. However, we'd like to provide some official guidance on our current recommended best practices. The Vue docs have a great page with their recommendations, why they recommend certain patterns, and how strongly they recommend them, and we'd like to do the same thing here. For example, we'd recommend using a "feature folder" or "ducks" pattern for structuring files, treating actions as "events" instead of "setters", and trying to put as much logic as possible in reducers.
- Adding a "Thinking in Redux" section. This would include concepts like mentally modeling actions, the history of Redux and its inspirations, how it's meant to be used, and why it works the way it does.
I'd love to have additional help from the community in working on this docs revamp - I certainly can't do it all myself :)
Finally, I'd encourage folks to watch my Reactathon 2019 talk on "The State of Redux" [5], where I talked about Redux's market share and how it compares to other tools, as well as future directions for the library. In addition, my post "Redux - Not Dead Yet!" [6] addresses some common questions about how things like hooks and GraphQL relate to Redux.
If anyone's got questions, feel free to ask!
[0] https://redux-starter-kit.js.org
[1] https://redux-starter-kit.js...
I'm still torn with Redux though, seems great for big projects/teams, but total overkill for small ones. I wrote a small alternative for my personal projects (https://github.com/franciscop/statux), what do you think about how it'd fare with your points? Looking for improvements, and I saw a bug once (though I haven't been able to reproduce and not 100% sure it was within the lib).
It makes it dead easy to cold read a components functionality and super simple to detect code smells.
Mobx-react[0] felt like much less work with virtually the same outcome. You create a single (or multiple stores), pass it into your app with a
and you are off to the races. When adding the global state to a component, it was super easy: Accessing the store: In the store you organize your code by observables (global variables), actions (functions that modify observables) or computed (highly optimized functions that return values derived from state). I treat it as a "source of truth" in a single file. In the end I felt that my code was far more readable and maintainable but your miles may vary.Here is a relatively simple example of how to use mobx-react demonstrating an observable, action, and computed: https://www.codementor.io/susanadelokiki/managing-state-with...
[0] https://github.com/mobxjs/mobx-react
The API surface of MobX is a breeze, and it kind of pushes you into the pit of success.
Hooks, imo, simplified a lot of these issues.
It's admittedly hard to come up with examples of actions that _aren't_ a 1:1 mapping in a small tutorial app, but maybe we can figure something out.
Related FAQ:
https://redux.js.org/faq/actions#is-there-always-a-one-to-on...