I created the same app with React and Redux
medium.com
medium.com
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!
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:
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.
>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.
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'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.
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.
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... import { bindActionCreators } from 'redux';
import { useDispatch } from 'react-redux';
import { useMemo } from 'react';
...
export default deps => {
const dispatch = useDispatch();
return useMemo(
() =>
bindActionCreators(
{
someAction,
anotherAction,
...
},
dispatch
),
deps ? [dispatch, ...deps] : [dispatch]
);
};
I also rely pretty heavily on redux-thunk, which lets me do simple workflows for async... export const someAction = (input1, input2) => async (dispatch, getState) => {
try {
dispatch(someActionStart());
const result = await api.area.someCall(input1, input2);
dispatch(someActionComplete(getState(), result));
} catch(error) {
dispatch(someActionError(getState(), error);
}
}
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
});
...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
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:
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.
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.
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.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...
The API surface of MobX is a breeze, and it kind of pushes you into the pit of success.
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.
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.org/tutorials/advanced-tutorial
[2] https://github.com/reduxjs/redux-starter-kit/issues/82#issue...
[3] https://github.com/reduxjs/redux-starter-kit/releases/tag/v0...
[4] https://github.com/reduxjs/redux/issues/3313#issuecomment-45...
[5] https://blog.isquaredsoftware.com/2019/03/presentation-state...
[6] https://blog.isquaredsoftware.com/2018/03/redux-not-dead-yet...
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...