Idiomatic Redux: Implementation and Intent
blog.isquaredsoftware.com
blog.isquaredsoftware.com
If you don't want to edit multiple files, use the "ducks" pattern to put actions and reducers in a single file [1]. If you don't like writing action constants by hand, use one of the dozens of action/reducer generation utilities out there [2] [3]. If you don't want to manage normalized data by hand, there's many addon libraries to help with that [4]. I specifically show off one of them, Redux-ORM [5], in my "Practical Redux" tutorial series [6].
I love all the tools and addons that people are building on top of Redux. One of my favorite quotes is from a Redux issue thread:
> Redux is a generic framework that provides a balance of just enough structure and just enough flexibility. As such, it provides a platform for developers to build customized state management for their use-cases, while being able to reuse things like the graphical debugger or middleware.
A few months ago I opened up a Redux issue thread to discuss further ways that we could help make it easier for people to use Redux, and solve complaints about "boilerplate". The discussion kind of trailed off, but I would love to have more discussion and ideas for building useful new tools on top of and around Redux to help people solve problems and build useful applications. Please join the discussion here: https://github.com/reactjs/redux/issues/2295
[0] https://github.com/markerikson/redux-ecosystem-links
[1] https://github.com/erikras/ducks-modular-redux
[2] https://github.com/markerikson/redux-ecosystem-links/blob/ma...
[3] https://github.com/markerikson/redux-ecosystem-links/blob/ma...
[4] https://github.com/markerikson/redux-ecosystem-links/blob/ma...
The docs themselves are written in a very "from first principles" style. For example, the page on "Middleware" starts by trying to show various ways to solve the problem of logging all actions, and iterates from "monkey-patching the store" up to a reusable system for wrapping `dispatch`. Some people find this approach very helpful, others don't. As Dan commented during the original development process:
> So hard to write the new docs. Many different audiences to cater to. Should make sense to: Flux beginners, FP people, FP people who don't get Flux, Flux people who don't get FP, normal JS people too. Flux people: “is this proper Flux?” FP people: “is this that weird thing called Flux?” Normal people: “why not Backbone”
Now, at this point, far more people are familiar with Redux than with "Flux", so perhaps the goal of making the docs understandable and relatable to Flux concepts isn't as important.
Most of my work around Redux has been improving the docs, writing my own tutorials and explanations on my blog (like this post), and answering people's questions elsewhere. I'm always happy to work with anyone who wants to help improve the Redux docs, and I'm totally open to suggestions and pull requests.
[0] http://redux.js.org/docs/FAQ.html
[1] http://redux.js.org/docs/recipes/StructuringReducers.html
That's not to say Redux is bad or there is no value to learning it though. It presents a really interesting take on state management and really opened my eyes to a huge amount of concepts. I'm just not sure I would want to go back to a heavy Redux project again having used MobX!
Redux doesn't have this, instead libs like reselect fills space.
My experience with redux was frustrating in the way you explained, but once I "got" how to defer all state to reselect instead of trying to do things with reducers it got significantly simpler.
I have referred your links. Thank you btw.
Looking like, and being like are two different things :) Mobx reminds people (rightfully) of Meteor, Knockout, Angular etc, but when working with it you will see it is quite a different best. Just like Redux will remind people of flux, but that doesn't mean people shouldn't try Redux because e.g. Facebook Flux didn't work out.
Yes, but they exist to solve very similar problems.
> when I look at mobx I get reminded of managing crazy state trees in ember and why I went to react/redux in the first place.
You may find it feels differently in practice. Because when I look at redux, I get reminded of managing crazy state trees in redux, and why I went to react/mobx in first place. :)
To be sure, Redux has strengths too, and there are tradeoffs. But I don't think that a brief "mobx looks like ember!" really conveys much, and I think the number of people who have been happy after switching from redux to mobx further undercuts such easy dismissals.
Also an important thing to note is that Redux is fundamentally designed to lend itself to one-way data flow with a well-abstracted barrier between global state and your components. Yes, you can use MobX to accomplish the same thing but the pattern is fundamentally less obvious especially for someone new to React.
That's the point I'm contending: Its not ridiculous because its not obvious that its an alternative. And not just an alternative, but a much much simpler alternative for many use cases. I absolutely agree its annoying, and I'll have to be that annoying guy for a while. Because I can't stand seeing people spend time on clever solutions to problems that, with mobx, you may not have.
Shameless plug: I created React Axiom for those who want a lightweight model pub/sub render paradigm with React:
Spent a lot of time researching for this post. What I really _wanted_ to write was the second half of Part 2 [0], where I gave my opinions on why certain usages are good ideas or bad ideas, but I realized I needed to cover a lot of background before I got there :) Was really cool reading through the original discussions and seeing the vision Dan and Andrew had for Redux from the beginning.
Hopefully this pair of posts helps clarify a lot of the discussion around Redux and its common usage patterns. Also, I'm always happy to answer questions about Redux (and React) usage, and encourage people to come by the Reactiflux chat channels on Discord [1]. Always a great place to learn and ask questions about React, Redux, and related technologies. Finally, if anyone is interested in learning React or Redux, I keep a big list of links to high-quality tutorials and articles on React, Redux, and related topics, at [2]. Specifically intended to be a great starting point for anyone trying to learn the ecosystem, as well as a solid source of good info on more advanced topics.
[0] http://blog.isquaredsoftware.com/2017/05/idiomatic-redux-tao...
I feel that every Redux blog post should start by pointing out that despite react/Redux almost being discusssed synonymously, as the author of Redux points out, "you might not need Redux".
I would go further and say "avoid Redux until you know you need it", and point people to more simple ways of reaching the same goals.
Redux is a power tool for experts that is disheartening beginners and sending them down the wrong path.
I was speaking to someone recently who had tried Reactjs. I wanted to enthuse with him about how awesome it is. He said it was too hard and he went with vuejs instead. When I questioned him further it became apparent that he had started to learn react/Redux and gave up in confusion.
Per your quote, Dan Abramov (creator of Redux) advises that people should focus on learning React first, and _then_ try to learn Redux. I'm one of Redux's current maintainers, and I completely agree with that advice. (My "standard advice for learning React" includes exactly that instruction.)
However, while it's absolutely possible to build a React app without an additional state management library, there _are_ excellent reasons to move most of your state outside the component tree: simpler props passing, optimized app performance with less re-rendering, and the ability to maintain more of your application's current state in the middle of hot module reloading. And, in the case of Redux specifically: benefits like time travel debugging, centralized middleware for dozens of use cases, more easily testable code, and the ability to straightforwardly trace data flow through the system.
A lot of the issues I see with people trying to learn Redux are more about people who either have little JS or React experience to begin with, or are completely used to OOP, and are having issues trying to get used to functional programming principles in general.
Is Redux for everybody? No. Should it be the first thing that a learner jumps into? Probably not.
Does Redux help solve real problems for front-end developers? Definitely.
(Note: In addition to "You Might Not Need Redux" [0], it's fascinating to read Dan's earlier pre-Redux article "The Case for Flux" [1], where he not only discusses the benefits of using the Flux Architecture to solve use cases like caching data on the client, but also argues against doing things purely out of dogma: "A dogma is born when the solutions are presented without enough original context, and newcomers feel pressured to delegate crucial decisions to an authority." So in that sense, yes, I'd absolutely agree that newbies are being pushed to use Redux out of dogma, but disagree that learning to use Redux is a bad thing.)
[0] https://medium.com/@dan_abramov/you-might-not-need-redux-be4...
The problem is because the community reveres Redux so highly that react and Redux are coming to be equated as a pair. Not much you Redux developers can do about that, it's a testament to the value of your work.
Also worth noting that Redux can absolutely be used with any UI layer, _is_ being used with frameworks like Angular and Ember, and has also inspired variations like vuex and ngrx/store.
I've seen this expressed many, many times, and it is something that I've honestly never quite understood. I started with React less than a year ago, and it wasn't until I added Redux to my stack that I began to feel perfectly comfortable with,
To me, the fact that actions dispatched in whatever component would cause the global state to be recomputed which then got handed to react to render it just a no-brainer. This truly makes data in React flow just one direction. The tooling around it -- Redux DevTools -- also made seeing my app in action and debugging it so incredibly clear.
Without it, for me initially it was a struggle about where exactly to store certain state, and passing props and callbacks up and down all over the place. It wasn't clean or easy to reason about at all.
As someone who had very limited experience in the framework, Redux felt like a tool which made the pit of success for me pretty darn large. I do know that even Dan himself has suggested that people not learn them in conjunction, but to me, the coupling of them is so perfect that React only became a joy to use WHEN I introduced it.
Well, if you're coming in from a non-FP background, learning Redux is intimidating. I'm a aspiring web developer (I'm coming from tech support role and my programming skill is mostly bash and bit of Python) and started looking into Redux, and yay struggled with getting started.
const store = createStore(reducer)
Took me hundreds of console.log ('entering func blah blah') before I realize out that those reducers become properties of the object store. So yes, Redux is power tool for expert and not as nearly accessible to your average neighborhood web developer.The amount of things you need to know in order to use react and redux is ridiculously low.
You're basically just composing functions of two types "(state, action) => state" , and "state" => html.
The boilerplate consists of a few functions (really mostly createStore and connect) that are incredibly well documented.
There's almost nothing to understand and redux itself is almost not a lib (only 318 LOC). The whole thing is a pattern and redux is just a small, very well written key component of the pattern.
When people debate redux they debate about programming, not about a tool. That's why it's interesting and why it will never end.
I think most people are more comfortable with object composition rather than function composition, so Mobx feels more natural.
I added a event emitter to AsyncStorage and that's basically it.
"If a CommentsStore needed data from a PostsStore to properly update itself, it could call PostsStore.waitFor() to ensure that it would run after the PostsStore updated. [...] with Redux, that sequencing can simply be accomplished by explicitly calling specific reducer functions in sequence."
And it introduces a specific call order of slice reducers, and 'hasCommentReallyBeenAdded' variable, inside the root reducer to implement that dependency.
But it looks to me like such a dependency will usually be specific to a certain action, and in a modestly complex application, different actions will have dependencies in different orders, with different ordering and information needs, and moving those into the root reducer doesn't scale beyond one (i.e. the shown example). Thus, as is, the section doesn't really provide a useful pattern for building real apps. Those will need a different approach - represented by the catch-all but not so useful "it's all about how you want to write it."
Am I missing something about that section and topic?
It's entirely possible that the `commentsReducer` does know how to handle all relevant actions. It's also possible that it only needs that extra data for one specific action.
One of the biggest advantages of the "reducer" concept is that _they're just functions_, and you can mix, match, and combine them in any way that works for you. I show several examples of additional custom reducer structures in the "Structuring Reducers - beyond `combineReducers`" [0] section of the Redux docs, and also in my blog post "Practical Redux, Part 7: Feature Reducers" [1].
As a quick summary, instead of having the `combineReducers`-generated function as your root reducer, you can further wrap that up in other functions, such as one that takes an array of reducers and runs them in sequence, or one that only does that special handling for a specific action case and otherwise delegates all handling to the normal `combineReducers` approach.
[0] http://redux.js.org/docs/recipes/reducers/BeyondCombineReduc...
[1] http://blog.isquaredsoftware.com/2017/01/practical-redux-par...
But if you're using components they way they're intended, (not cramming everything into a few overloaded components), you're quickly going to be passing down props down through layers and layers. It will quickly become a maintenance nightmare.
Just imagine that you have a button component that is 5 or layers down from you're stateful component. It's getting props to control it's state passed down through all 5 levels, and it's getting functions to change the stateful component's state passed down through all 5 levels. Now what happens when you want to move the button from your side bar to your footer?
Redux solves a very real problem, there are other solutions to this same problem, but eventually any non-trivial app is going to need something for state management beyond a single top level stateful component.
I talked about some of the benefits of using Redux in a React application in a post I co-wrote: https://www.fullstackreact.com/articles/redux-with-mark-erik...
Have fun testing that!
If you're having difficulty moving components around because of state consider that you may be thinking too statefully and should try to find a less stateful way to describe your UI, or that the UI you have constructed does not adequately group related pieces of data and functionality together, which will lead to a confusing UX.
Having one function 5 levels deep that receives its props from a top level function means everytime you add a prop you have to modify 5 function signatures to get that new data. That quickly becomes unsustainable when you have 10+ layers.
What happens when you want to modify the prop that is set in the parent? You have to pass through a callback function that will modify that parent props and then trigger a re-render.
Very quickly you end up with problems that flux/redux/mobx try to solve.
It was never built to be used that way. Passing down chains of attributes and functions through 5+ layers isn't idiomatic React because React was created with Flux in mind--this is not the intended methodology.
>If you're having difficulty moving components around because of state consider that you may be thinking too statefully and should try to find a less stateful way to describe your UI
That is a meaningless platitude. State exists, you can't remove it. No matter how much you minimize it, you're going to eventually end up passing props down through an arbitrarily large number of layers if you have no state management system in place.
Language designers realized this was a problem decades ago--it's why we have scoping rules. Passing down chains of arguments through layers and layers of functional calls is unwieldy. And it's worse when each one of these functions is directly producing user output.
The biggest problem I struggle with is knowing when components rerender. Libraries like reselect that memoize state-composed functions really help with performance, but make it less clear when things are being rerendered and why they are being rerendered.
I also agree with some of the posts here that handling async actions, or side effects, can be rather difficult in the redux model. Redux-thunk is still being recommended even though Dan created it more as a bandaid solution rather than something more robust. We solved it with redux-saga, but my colleagues -- experienced developers -- struggled initially to grasp generators. I also think sagas run into the same problem that one action triggering multiple reducers has which is you could inadvertently trigger an action that hits one or more sagas and not realize you are causing side effects that you did not intent. I know there have been countless discussions on what to do about this problem, but I fear the solution will always be outside of the scope of the core library because of backwards-compat.
I'm a bit curious why you say that "reselect makes it less clear when things are being re-rendered". The overall reasons shouldn't change - connected components will re-render when values from `mapState` or props from the parent component have changed by reference.
Thunks are definitely the "minimum viable approach" for handling async logic in Redux, or at least the minimum _suggested_ approach (as opposed to doing async handling purely inside of a component). I agree that sagas (and observables) are a pretty big jump for most devs to make, but then again, async logic in general is more difficult to reason about. Personally, I still think most devs should use thunks until they see a need for something else (per my post "Idiomatic Redux: Thoughts on Thunks" [1]).
Redux does deliberately push the question of "how do I handle async logic?" outside of its core, and leave it up to you. That's both because Redux focuses on structuring synchronous state updates, and because (per my post) Dan and Andrew wanted to give devs flexibility in what specific technique they use for handling async.
[0] https://github.com/markerikson/redux-ecosystem-links/blob/ma...
[1] http://blog.isquaredsoftware.com/2017/01/idiomatic-redux-tho...
In UI programming you are managing state, full stop. Almost any UI can be described as a state machine. Not just GUIs, but any UI: digital, analog, mechanical, whatever. And since you are basically managing state machines, any refactoring of the functionality of your UI is fundamentally a refactoring of your state. Rendering is a functional refactoring (state doesn't change), but if you are building a UI and not just a simple landing page, you are far more likely to refactor functionality over rendering...meaning your data is more likely to change than your functions. In other words, UI programming via functional composition over a frequently changing global state tree falls on the wrong side of the Expression Problem [0].
Every time you change your data, you have to modify every functional pipeline, from beginning to end, that touches that changed section of your state tree. That is where all the boilerplate comes from, and that is why Redux is consistently criticized as a system that becomes more and more unwieldy as your UI complexity grows. It is a bad abstraction for the problem at hand.
Mobx is a far better pattern IMO, and I feel entirely comfortable saying that is an objective position to take as long as your functionality is more likely to change than your rendering. It isn't that Mobx is a superior idea, it's just the fact that it embraced object composition for a use case that is perfect for it.
To keep it simple, I'm not using react-redux (I often try to avoid this), and I generally pass the entire store down through all child components, again to keep it simple. My top level app with its simple navigation code subscribes to the redux store and sets its state from it. My router is just a bunch of statements like {pageName === 'connnections' && <ConnectionsPage {...props}}.
To avoid too much boilerplate, I find most of the time all you need are some helper functions. I have a helper function called 'action' that I call like action('LOAD_SOME_DATA') that dispatches on the store with type: 'LOAD_SOME_DATA'. I use a simple 'getIn' function to access nested data, and I mutate data in my reducers with object-path-immutable. Everything is clean and easy to read.
I believe one of the biggest problems with the React community is the constant bleed of 'performance' into app design and development. Premature optimization is everywhere.
Guess what, most apps won't need immutable data and free componentShouldUpdate checks, memoized selectors, or subscribing to subsections of the store using complicated 'connect'/high order component plumbing.
The one time I actually had a perf issue with React was when I had a GIGANTIC list of data (showing all the active salespeople in our company -- we have a lot), which due to me storing form state in redux was being completely re-rendered every keystroke.
The solution wasn't to add react-redux or start using immutable data - it was to add paging and filtering. I improved performance, but more importantly, the UX of the app with this.
If your React app really is so huge and complex that it needs reselect, react-redux, react-router, great stuff -- you are obviously building software for a successful product with lots of feature requirements. Most apps I've worked on never got to this level of complexity.
You can circumvent this by creating a component at the top level of your application which holds the state and passes it down via React's context. The state-dependent components can then pull the state from context where they need it. By doing so, you basically reinvented react-redux, which works just like this (+ the performance optimizations).
Regarding your issue with the complete app rerendering on a single keystroke: This is a result of you passing down the entire store to each component. Since you still edit the store in an immutable way, on every keystroke you create a new store object and React rerenders everything because the store prop changed. I'm also confused how using paging/filtering would help with this.
Another advice would be to not store form state in redux. Keep your redux store only for your actual model application state. Keep forms/temporary state in your components and move it to your model redux store once it is done/baked.
There was a similar thread on Reddit a few days ago, asking why the OP should bother using React-Redux [0]. I'll paste the main part of my reply:
> First, while you can manually write the code to subscribe to the Redux store in your React components, there's absolutely no reason to write that code yourself. The wrapper components generated by React-Redux's connect function already have that store subscription logic taken care of for you.
> Second, connect does a lot of work to ensure that your actual components only re-render when they actually need to. That includes lots of memoization work, and comparisons against the props from the parent component and the values returned by your mapStateToProps function for that component. By not using connect, you're giving up all those performance improvements, and your components will be unnecessarily re-rendering all the time.
> Third, by only connecting your top-level component, you are also causing the rest of your app to re-render unnecessarily. The best performance pattern is to connect lots of components in your app, with each connected component only extracting the pieces of data it actually needs via mapStateToProps. That way, if any other data changes, that component won't re-render.
> Fourth, you're manually importing the store into your components, and directly coupling them together, thus making it harder to test the components. I personally try to keep my components "unaware" of Redux. They never reference props.dispatch, but rather call pre-bound action creators like this.props.someFunction(). The component doesn't "know" that it's a Redux action creator - that function could be a callback from a parent component, a bound-up Redux action creator, or a mock function in a test, thus making the component more reusable and testable.
> And finally, the vast majority of apps built using React and Redux use the React-Redux library. It's the official way to bind the two together, and doing anything else will just confuse other developers looking at your project.
> So, part of the issue is you're not using React-Redux and connect, and part of the issue is you're using an inefficient pattern for pulling data from the Redux store into your React components.
So no, many apps don't need to spend tons of time optimizing performance, but at least part of that is because `connect` already handles a lot of that for them. There's also other reasons for immutability, per my original post for this thread: time travel debugging, testability, and ability to reason about state updates.
Also, in regards to the "re-rendering on every keystroke" issue, my post "Practical Redux Part 7: Form Change Handling" [1] shows off a React component I made that specifically helps buffer those for fast form updates, while debouncing the number of Redux actions dispatched.
[0] https://www.reddit.com/r/javascript/comments/6hperk/i_use_re...
[1] http://blog.isquaredsoftware.com/2017/01/practical-redux-par...
Second and third points both concern performance. See my OP: it's a premature optimization. How many people honestly run into rendering performance issues when building a single page app? Why introduce extra complexity for performance you don't need?
Fourth point: no, I don't manually import the store into my components. My top level app component subscribes to the store (3-4 lines of code), and passes the data in the store down into its child components, including my routing component and all the rest. Most of them are dumb stateless functions that just render from their props and call the occasional action function.
Action functions themselves aren't on props and don't do any binding of any kind. They are just functions that provide a simple abstraction on top of store.dispatch. Again, simplicity.
Regarding confusing developers: I disagree with this statement 100%. We have two React apps at work, one that uses the simple approach without react-redux, and another that pulls in most of the usual ecosystem: react-redux, react-router, redux-form, and so on. The cognitive overhead is larger; reasoning about the code is harder; the greater number of libraries means we've had bugs in those libraries that cause difficult to solve problems.
It's just an opinion of mine, but in my experience, React and redux by themselves can get you a very long way before you need to pull in other libraries.
Thumbs up on Redux, your boilerplates are my sanctuaries.
The original point of writing it is that I see many people complaining about things like "having to use action creators" and "having to edit many files", when Redux itself doesn't actually require you to do those things.
So yes, I guess you could say that I _am_ trying to enlighten people, because many of the complaints about Redux are being made out of an incomplete understanding of why it exists and what its intended use is. If people still don't like Redux after that, that's fine - I just want people to better understand why Redux usage patterns exist and clear up the misunderstandings.
From spamming his links everywhere he can, to begging for upvotes on Twitter, going by posting stuff about how he got famous and constant bragging, it's not new.
These kind of comments are disappointing to read, especially when directed at someone sacrificing an awful lot of time helping others.
Redux takes a simple concept, and turned it into something thats really hard to apply to the projects these people are writing; by reinventing words into a propriety language.... No one talks about about there state as a function of "reducers". And when they think they have their head around "actions" they realise that no, its not a 1-to-1 relationship with what the user is doing.
If redux didnt try and re-invent event-sourcing with its own "idomatic TAO way", it could of leveraged off the plethora of well written documentation on the subject.
Instead, Dan, thought he could improve event sourcing and make it palatable for the masses by -replacing "events" with "actions" [1] -making the state an anemic domain model[0] -persisting snapshots instead of an event log then adding on congnitive load by implementing a diffing algorithm for UI updates which requires specialised knowledge. [1]
As to your other point, you're right, and it is somewhat of an issue: Redux does work better when it's thought of as event sourcing. And it can (and IMO should) be used as one. When actions are nearly 1:1 with what is happening in the system, and you're simply doing a "fold" on the event log to then project it to a stateless UI, it works beautifully.