Redux offers a few benefits:
1. Allows you to keep your global application state 100% predictable by treating it as a pure function of an event stream (dispatched actions). This makes it trivial to test.
2. Allows you to "time travel" the state of your store using the Redux dev tools by replaying/modifying the event stream. You can also set up QA folks to export their events so that developers can reproduce any bugs in the global state 100% reliably.
3. Allows you to access global state without explicitly passing down props from great-grandparent to great-grandchild.
While I'm not a fan of using Redux for everything (it does have costs), you mentioned only the most minor benefit. If you only want #3, you would just use React's built in context API.
The issue with Redux is that it encourages side effects in components. After a while people are dispatching all over the place and you end up in a similar situation as you were with keeping track of JavaScript events. All of a sudden something stops working because someone removes a component that is dispatching on a timed interval etc.
If you can convince and enforce that all Redux activity only happens at the root component, you should be okay. But the reality is that once you have the discipline to only mutate state in your root component, then setState tends to be more than enough for anything but very large and complex interfaces.
With that said this article has the author implementing their homebrew bespoke event system which is likely even worse. This was a very popular and much maligned anti-pattern during Angular 1.x days. This article is suggesting the absolute worst possible idea for maintenance.
What kills the maintainability of React and similar frameworks is tracking down what component did what. Unfortunately, in real world examples, in my experience, Redux increases that issue and ultimately leads to a very difficult application to maintain long term.
The only maintainable projects I have worked on in terms of web apps have been with Redux.
Redux is not great for simple projects, it is great for medium and large scale projects.
> All of a sudden something stops working because someone removes a component that is dispatching on a timed interval etc.
That's the problem: react and redux are not enough for large projects, you also need a way to manage side-effects. Incorporating `redux-saga` would fit that purpose.
React, redux, redux-saga -> this is the recipe for large scale applications that I have used, built, and helped maintain with success.
One of the strongest benefits of redux is being able to see every single event in a debugger that displays current state, action payload, next state. It really does make tracking down state changes a matter of reading a global log.
Furthermore, because redux is not dependent on react and under the hood it is simply and event emitter, it becomes relatively easy to use the same exact business logic for one web app and use it for some other delivery.
We did this at my last company: because our business logic was based on redux and completely separate from the UI layer: we were able to build: a cli, chrome extension, outlook plugin, and mobile apps using the same exact business logic.
I'd say that the only times I've seen Redux work effectively at all is when the project was small enough that the complexity remained manageable.
> > All of a sudden something stops working because someone removes a component that is dispatching on a timed interval etc.
> That's the problem: react and redux are not enough for large projects, you also need a way to manage side-effects. Incorporating `redux-saga` would fit that purpose.
I don't need a way to manage side-effects, if I don't use Redux...
I'll admit I haven't used Redux-Saga, but you'll excuse me being skeptical that introducing more complex tools will solve my complexity problem.
> We did this at my last company: because our business logic was based on redux and completely separate from the UI layer: we were able to build: a cli, chrome extension, outlook plugin, and mobile apps using the same exact business logic.
Again, this is not unique to Redux. It's perfectly possible to have your business logic completely separate from the UI layer without Redux. In fact, my experience is that Redux tends to cause devs to mix UI logic into business logic (and any sufficiently complex UI will have its own state and logic separate from the business logic).
I'm a consultant and in my experience this misconception is by far the most common source of problems in redux projects. Redux is a very small and simple thing so you have to build a framework around it. Even redux-thunk isn't enough - that's only 5 lines of code.
I wouldn't recommend react/redux for public facing sites if there's no experienced, pure frontend team. A more batteries-included kind of framework would be better. There's a lot of thinking to do and hardcore levels of scaffolding to maintain before writing anything useful and maintainable with redux. But it can absolutely lead to maintainable applications.
Also, I've never seen any homegrown "vanilla" frameworks or data stores work well as complexity increases. It usually ends up as spaghetti, especially with a growing team + growing business demands. Or best case a poor re-invention of the wheel. There must be brilliant counter examples but they're probably rare, I may never have the pleasure of working with one.
FWIW, we've got a new Redux Starter kit package that tries to be a bit more "batteries included". It includes utilities that help simplify several common use cases, including store setup, defining reducers, immutable update logic, and even creating entire "slices" of state automatically. It also includes `redux-thunk` built in, as well as some middleware that check for things like mutations.
Please try it out and let us know what you think!
I understand why folks have opted to create higher-level frameworks around Redux like Rematch, and it's not "wrong" that they've done so. But, looking at Rematch code, you can't really tell that it's actually Redux underneath.
With RSK, I've aimed for an intermediate level - decreasing the amount of things you have to write by hand and the literal number of keystrokes you have to type, but preserving the idea of "dispatching actions" and using reducers.
Glad to hear that you think it looks good! If you've got any additional feedback or suggestions, please let me know.
So far every way of doing UI I have seen isn't maintainable long term. Yet somehow things wind up being maintained even if it is in that "why me?" kind of way.
I think this says less about frameworks/libraries themselves and more about the amount and sources of entropy present in the front-end. That's really what we're dealing with here.
Libraries/tools/patterns/frameworks all usually have the function of containing some of that entropy. In theory the best tool contains the most entropy while allowing you to produce results you can sell to all stakeholders.
I think over the years we are doing better and better, but it's a long game. We're not all the way there yet, is it even possible to get all the way to the dream?
Which is why a lot of people that encourage Redux tend to also encourage one or more of the "side effect" managers such as thunks, sagas, or observables (my preference).
From my perspective, React is the thing with the problem: it doesn't have great state management for complex tasks / multi-component coordination. What I want to use is something like RxJS to manage such interactions and state, because it gives me a lot of power/control in a relatively easy way. One of the best options out there that I've seen is redux-observable, and thus Redux itself is just the "stuff inside the oreo" gluing React to all my interesting "epics" describing high level functionality in the applications.
It's also the Unix philosophy thing of use tools that "do one thing well" and then chain them together. React does view components rather well, Redux does state management rather well, Redux-Observable does state "interaction" rather well. Chained together they work remarkably well.
I liked it more than redux, because you can have have simple class implementation and side effects without much hassle. Also TS support and testing is easy.
(Which is an interesting turn of events, personally, because I really liked Knockout ages ago, but its attempted successors in MobX, Aurelia, and even Vue leave me cold. I think partly because Knockout was so much more verbose and forced/needed a clearer "MVVM" separation simply by how "dumb" it was, whereas Proxies and Descriptors gives too much of an illusion that you are working with "plain" objects that don't need a clear "MVVM" separation until it is sometimes too late to fix a maintainability/tech debt hole.)
Sure, you don't need Redux in the middle of the "oreo", but it's also not the "wrong" answer, either.
The observer pattern is mentioned in the Gang of Four Book[1], published in 1994. JavaScript first appeared in 1995. Observers predate Redux. You don't need Redux to use observers, and in fact there's nothing about React that is incompatible with RxJS.
> It's also the Unix philosophy thing of use tools that "do one thing well" and then chain them together. React does view components rather well, Redux does state management rather well, Redux-Observable does state "interaction" rather well. Chained together they work remarkably well.
Don't sully UNIX philosophy by comparing it to Redux! UNIX is such a different environment from JS space that it's hard to really even compare the two, but at the very least I think we can agree that "Doing one thing well" in UNIX means not creating a bunch of new problems that you need other tools to solve.
[1] https://www.amazon.com/Design-Patterns-Object-Oriented-Addis...
Yes, and before I was using React+Redux+Redux-Observable I was using Cycle.JS and everything was RxJS and/or Xstream (an RxJS-like) observable-based patterns.
As I said, this stack is a useful "oreo" for my needs right now. I understand if "oreo" isn't your cookie of choice.
> at the very least I think we can agree that "Doing one thing well" in UNIX means not creating a bunch of new problems that you need other tools to solve.
Again, I think the problem here is that we are disagreeing on what the problems even are. Again, I don't think these tools "create problems", I believe they solve very specific problems, and yes very "unix" in that way of solving as specific a problem as they can and no larger, and leave other problems that already existed untouched, whether or not they were in play, because again that's the philosophy here. React is "just" a view layer with a bare modicum of state responsibility. Redux is "just" a state layer. Redux-observable is "just" a tool for handling state side-effects. They don't need to solve every development problem, this isn't Angular. Similarly, they aren't the only tools for the job. There are several alternatives to each part of that stack (as others in this thread keep pointing out), this is just the one I've chosen for my projects right now.
Reacting to object changes (persisting them to a database, or otherwise audit logging them).
Kicking off and managing "background tasks" like chron-job equivalents (5 minutes after a state update do some other related thing, such as a automatic state machine transitions), database synchronization/replication processes, cross-checking/merging in data from GPS/geolocation/compass streams, etc.
When I hear someone complaining about redux using global state, I like to ask them what makes global state bad. The answer is usually that it allows any part of the application to change a value, which makes debugging difficult. This doesn't apply to redux and similar frameworks because every slice of the state can only be managed by a single, pure function, which makes it incredibly easy to debug. Additionally, redux uses a copy on write mechanism, so your state tree isn't actually being mutated, which allows you to use equality comparison instead of a deep compare to see what state has changed. That, combined with the fact that all actions go through a central dispatcher that is easily audited means unit testing your state transitions is almost laughably trivial. And since you are pulling all of your business logic out of your UI components, you can avoid the side-effect hell of unit testing your UI.
Having said that, I won't use react or redux for any website that doesn't have a heavily interactive UI with complex state that must be shared across many pages. Managing application state in your front end is an enormous burden and you'd be crazy to take it on without a need for it.
I respectfully disagree here. All UI views in React can be controlled via parameters. Very few components in a React application require state management. You can add parameter functions to handle various events as well.
> which allows you to use equality comparison instead of a deep compare to see what state has changed
You are doing a lot of deep compares on state in a real world application? Why? If you only use parameters for the state of your component, everything is already handled by React out of the box.
> every slice of the state can only be managed by a single, pure function
Really? You can trigger reducers from literally anywhere in your application. Not only that, you can create multiple reducers to handle each action type. I've seen a situation with 24(yup, 24) reducer functions for a single action type. Many of those reducers contained complex business logic checking the overall state of the application. Then you have 2 or 3 action functions all with different business logic for that one type. Race conditions everywhere. setTimeout 0, here we go again. Step debugger hell. Of course, you should never do this, but in the real world, this is what happens over and over again.
> means unit testing your state transitions is almost laughably trivial
If you use parameters for state of a component it is 1,000 times easier. Because a component's view is not coupled to the global state of your application. Pass it parameters, test that it looks correct. Change a parameter, check if it is correct. Dead simple. Your tests are not bound to global state at all. Again, you started with an anti-pattern of managing state in child components, and now, instead of not doing the anti-pattern, you are adding yet another tool to fix it. Instead of just not doing it.
In summary, what you are describing is the "theory" of why Redux and associated tools should work. In every case that I've seen(10+ at this point) that has not happened. Not because Redux is a bad tool. Its not. But in the real world, it has become incredibly expensive for many organizations to maintain.
I agree. My apps usually consist of a few stateful components for dropdowns and modals and such with the remaining being functional components. The problem is the majority of "legacy" react apps I've come across seem to have the opposite ratio (I personally think this is because most react tutorials are written for fresh out of bootcamp developers by slightly more experienced fresh out of bootcamp developers).
> You are doing a lot of deep compares on state in a real world application?
Not a lot, but when you need it you really need it (usually for shouldComponentUpdate, but most recently I needed it for synchronizing state to localStorage).
> I've seen a situation with 24(yup, 24) reducer functions for a single action type.
You're right, that is insane. I personally use thunks or sagas to dispatch multiple actions that are specific to the domain model being updated and never update multiple slices of the state tree with a single action. For example, the action dispatched by clicking the sign in button should not update the user profile and the navigation state. Again, the problem in your example is not redux, it's a lack of separation of concerns.
> Become a components view is not coupled to the global state of your application.
The state of your application has to go somewhere, and if you are creating a small amount of stateful components and a lot of functional components, you are doing a lot of prop drilling, and every time the stateful component is updated the entire component tree gets re-rendered. If you keep the stateful components at the minimum height necessary, your business logic will be split up according to where it needs to be used instead of logically grouped by feature.
> summary, what you are describing is the "theory" of why Redux and associated tools should work.
You are arguing that redux is a poor choice because bad developers write bad code. I'm saying that the constraints that redux imposes on the structure of your code makes it easier to enforce separation of concerns as the app scales in complexity. The things about redux that most people complain about are the things that make it work so well in complex applications. I have seen unmaintainable apps that used redux, and equally unmaintainable apps that used component state. I don't think anyone can accurately say that one is worse than the other in a pathological case. At this point we're just arguing about opinions, but I maintain that explicit structure using shared conventions is still better than ad-hoc structure that requires institutional knowledge to navigate, even in the hands of untrained code monkeys.
One thing I have noticed is that every time react or redux comes up on HN it starts a flame war with the same arguments rehashed over and over, and when I get involved in these discussions none of my comments ever get any up or downvotes, which tells me that nobody is reading this and we should get back to work. :)
If there some kind of state that doesn't neatly fit into these solutions? I know that passing around signed-in user objects is one popular use case for Redux, but in my opinion that could be easily placed into localStorage, or assigned to a window.global_state variable.
This is a huge pain in the ass and requires a ton of really tricky UX design. What happens when the data is cached and you fetch it again? Do you disable the UI and show a spinner until the request is complete (why bother caching the data in the first place)? Do you allow the user to continue making changes while the request is pending and try to reconcile conflicts when the payload is parsed (this problem is far beyond the skill level of most developers)? When the data is loaded, do you just change the content out from under the user or do you write a series of complex animated transitions to make it less jarring?
Updating state on every transition is hard, and keeping two copies of the data is also hard (cache invalidation!), which is why you shouldn't use an SPA framework unless you absolutely require it.
> If there some kind of state that doesn't neatly fit into these solutions?
The type of state is completely orthogonal to whether redux solves more problems than it creates. It should not be included by default on every project, and there are thousands of alternatives that might be a better fit. The heuristic for whether you should reach for something like redux is the size and complexity of your app, not the type of data you have. If your app consists of a single route without a lot of branches in your view tree and most of your components are only used once, you definitely don't need a state management library. Once you start adding multiple pages and lots of component reuse, something like redux starts to increase structure and maintainability at the cost of additional complexity. There is also a lot to be said for using an architecture that most of the react community is already familiar with so new developers can ramp up quickly.
> Persisted to localStorage
This is a standard practice, but you have to be careful not to spam localStorage with updates since it's significantly more expensive than in-memory object creation. I haven't profiled it but I wouldn't be surprised if it were several orders of magnitude slower. I sync the application state to localStorage in all of my apps, but I debounce it by 500ms and use equality comparison to prevent writes when no changes have been made. Obviously none of this has anything to do with redux, but redux's copy on write mechanism makes comparing states trivial, and any code that mutates the state trees requires an expensive and tricky deep comparison that negates the benefit of making the comparison in the first place (especially in the case of shouldComponentUpdate).
> assigned to a window.global_state variable
This is not the same as globally available redux state. For one, redux does all of the plumbing for subscribing to state changes and preventing unnecessary re-renders. You could spend a couple of days writing your own, buggier implementation, but why would you? Secondly, as I've argued elsewhere in the comments, the redux state tree doesn't have the aspects of global variables that make them such a dangerous anti-pattern. Writes are isolated to a single function, and all other references are read only, and it uses a synchronous message passing system with a central dispatcher that is trivial to audit. Another commenter complained about race conditions, but redux core is only designed to support synchronous operations, and there are well tested and widely used tools for managing complex, multi-step asynchronous state updates. If you have race conditions you are using the wrong tool for the job.
This is a bit off the topic of your original questions, but every time one of these redux conversations comes up there is so much vitriol from people who just absolutely hate redux for some reason, and almost every time every reason they give for why redux is a cancer is a well known anti-pattern. I guess they tried redux, hated the verbosity and saw botched implementations by other developers unfamiliar with redux, and decided that redux was the problem instead of lack of experience.
"The framework has to be idiot proof" doesn't really hold water for me, since I would love to see a react stack that scales in complexity to hundreds of components and dozens of routes that isn't horribly complex and deadly in the wrong hands. The root cause is that react is a view library, not a framework, and there is no standard architecture to provide a common language between teams and developers. Since every project is unique there is no knowledge base of best practices that aggregates over years like rails or django or laravel. A massively complex SPA _absolutely requires_ a strong, experienced lead to enforce good practices and maintainable architecture. If you have a free for all with a bunch of junior and mid-level devs you are going to get a pile of crap no matter what stack you use.
I am not a zealous redux partisan, I just prefer to work with it because I know it well and I believe it uses a pattern that scales well by keeping business logic isolated and broken up into small pieces. I'd be perfectly happy to recommend something like mobX. The only thing I'm strongly against is mixing application state with view state. Anyway, I've spent several hours and put a lot of thought into responding to questions on this thread and some of the comments have been pretty rude, frankly (not yours). I would like it if people could discuss this topic politely and constructively, and not go into it with their mind already made up and ready to do battle with anyone that disagrees with them.
Rant over :)
So like I said, I've basically decided that Redux would be a great benefit for maintaining a consistent, easily debugged architecture for an app of this complexity, but then I came across several people talking about shared global state across routes as a good heuristic for knowing when Redux is appropriate. As mentioned, I don't really have that in my own app, so I was a bit confused by this and wanted to get some more details from you. I think you've cleared it up pretty well.
I'm completely in agreement with you about the likely source of dislike and complaining for Redux. Absolutely people jump to it too often when it isn't necessary. More generally speaking, I think there's a default attitude that front-end should be easy, and then people are rudely surprised when they find out that it's actually very hard. Distributed systems problems, cache invalidation problems, combinatorial scaling of complexity with every feature and source of user interaction... you already know this. They blame the team or the tool, but there's a fundamental complexity there that's unavoidable and deserves respect.
Anyway, thanks again, I've enjoyed reading your opinions on this.
This sums it up so succinctly I'm going to borrow this next time I'm discussing react with one of my C# teammates. :)
If you want to chat more my email address is in my profile.
2. This is only marginally more powerful than a debugger.
3. I would consider this allowing you to shoot yourself in the foot. Explicit is better than implicit, and explicitly defining your dependency tree is a benefit, not a downside. When you're feel the small pain of having to explicitly define a data dependency, that prevents you from introducing it if you don't have to, thereby avoiding the much larger pain of having an overly complex dependency graph. By adding Redux you're adding hours of debugging time on complex global dependencies to save yourself a few seconds worth of keystrokes.
As to other comments about logic in the View or State, it's bound to happen... the two aren't actually divisive concerns. Personally, I'm happier to use Redux than seeing prop drilling everywhere.
https://medium.com/@dan_abramov/you-might-not-need-redux-be4...
I'm not sure why you think the data is irrelevant, or why the component manages it "weirdly".
I'll point out that these top components actually share a lot in common with Redux, except that they can be duplicated and composed with other components, if for example you decide to drop your top component inside of a larger application, and the toplevel components actually don't end up managing data they don't have to (LESS "irrelevant" data than Redux, perhaps?).
I believe the GP meant "unrelated". Can you drop your top level component into another application with different children and still have it work? Can you move the child components to another view tree and still have them work? It seems like what you are describing is a top level component that is tightly coupled to its descendants with too many responsibilities. In the ideal react app architecture you should be able to take any component and move it anywhere else in the app and it should Just Work. This is the problem redux is supposed to solve, and I would argue that by enforcing an explicit pattern that most experienced react developers know, you will get closer to that ideal than if you have a variety of home grown solutions that vary from project to project (and even from developer to developer on the same project). "The Tyranny of Structurelessness" etc etc.
Yes, it's a matter of passing the child into the parent as a prop.
> Can you move the child components to another view tree and still have them work?
Yes, this sort of thing is exactly what you get for free by doing things the way I describe.
> In the ideal react app architecture you should be able to take any component and move it anywhere else in the app and it should Just Work.
This is exactly not what happens in Redux projects in my experience.
Sorry to beat this dead horse, but how does redux, which binds data directly at the level of the component that displays the data, make portable components more difficult than vanilla react where stateful components and functional components are tightly coupled? Do you have links to a gist or something so I can see what you're describing?
Sure, global variables make debugging difficult when you don't know which function changed them where and when and why. But that doesn't happen if you keep your functions pure and only make changes from within reducers – at least that's my experience.
(Boilerplate, yes, Redux adds that. But at least it's all in one place, and not scattered throughout your app.)
Sticking to reducers IS a good idea and it does make things a bit better. The problem with global state goes deeper than that, though.
Often, as you learn about the structure of the data, it becomes useful to change how the data is structured and stored. In global state, you can change that in reducers, sure, but then all the places that render that data have to be updated. You don't have the option to represent the same data in different ways that might be more suitable for different parts of the application. You either have to change them all at once, or synchronize a bunch of states that really contain the same data (which is even worse).
With independent components, you do spend some time synchronizing data across components, but each component has the option of how to represent its data internally, so the effects of changing data representation in one area are usually very local, and each area of the code gets to represent the data in a way that's reasonable for what it's doing.
I'll also add that with independent components, you sometimes get components whose only real job is to manage data. That's actually sort of like a Redux! But you can spin up multiples of them without having to manage an array, and compose them or even nest them without issues.
> (Boilerplate, yes, Redux adds that. But at least it's all in one place, and not scattered throughout your app.)
To be clear, I don't really care about boilerplate. It's easy to generate that stuff, but even if you write it by hand it's faster than debugging implicit, hidden complexity. Boilerplate ISN'T the problem with Redux, as far as I'm concerned.
> Often, as you learn about the structure of the data, it becomes useful to change how the data is structured and stored. In global state, you can change that in reducers, sure, but then all the places that render that data have to be updated. You don't have the option to represent the same data in different ways that might be more suitable for different parts of the application. You either have to change them all at once, or synchronize a bunch of states that really contain the same data (which is even worse).
or this:
> With independent components, you do spend some time synchronizing data across components
I'm not even hesitating before I choose the first option. My editor, static analysis, and the mystical art of the function can give me all sorts of help with the first problem. Good luck with those sync bugs.
Futher, Redux doesn't solve synchronization issues in situations where you can't represent the data the same way. In the most complicated situations, it actually exacerbates the issue because it separates the data from the components that use it, making static analysis tools unable to see how your data changes might break the components. This also goes to show you didn't even understand the part of what I said that you selectively quoted, since I already said this.
> I'm not even hesitating before I choose the first option.
Perhaps if you hesitated a bit more, you could use that time to read all of the options and understand them.
I would argue that synchronizing data between multiple representations is an anti-pattern, and the exact anti-pattern that redux, with its emphasis on a single source of truth, was designed to solve. Synchronizing state is almost always going to lead to hard to diagnose bugs in edge cases. There's even a blog post on why it's a bad idea on the official react blog [0]
My solution to this problem is to use computed values generated by a library with memoization like reselect. I structure my data as close as possible to the API data models and use selectors to transform the models into the data structures that disparate UI components require. If you need to change your UI component, you don't need to refactor your store, and if you need to change your store structure, you don't need to refactor your UI components.
[0]: https://reactjs.org/blog/2018/06/07/you-probably-dont-need-d...
> Futher, Redux doesn't solve synchronization issues in situations where you can't represent the data the same way.
It's kind of the whole Redux philosophy that you won't do that. It's in the FAQ. There's no "can't". You're never forced to keep duplicate data in your store.
Restrictions are the price of solving hard problems (even if you don't want to admit they're hard) in a deterministic fashion.
I'm not saying there aren't solutions to the problems I'm bringing up. I'm saying you don't have to solve them in the first place if you don't bring in Redux.
You keep saying that you won't have these problems if you don't use redux, but I am arguing that _you do have these problems_, you're just creating ad-hoc solutions for everything that you would use redux for, except those solutions are sprinkled into your UI components in a way that makes them full of mutable state that is impossible to test and a constant source of bugs. Note that I have already agreed that there is a scale where redux is overkill and creates more problems than it solves. Once your app has a few dozen components it's time to start thinking about another layer of abstraction.
And then you're right back to any component that uses User being tightly coupled to the structure of the User, without any of the syntactic cues that allow you and your static tooling to connect the two.
> except those solutions are sprinkled into your UI components in a way that makes them full of mutable state that is impossible to test and a constant source of bugs.
That's not the case:
1. There's nothing preventing you from treating components as a pure function of their props.
2. At the top level, it's necessary to have some mutable state, but I'm not sure where you get the idea that this isn't testable. Redux doesn't remove mutable state: all redux is doing is a big complicated version of newState = pureFunction(oldState) which is perfectly possible to do using Vanilla JS functions.
If you keep your state in a component, you don't need any housekeeping, once the component is gone, the state is gone.
This is not true with Redux. You have to keep remembering to clean the state after you're done with something or you're going to introduce a bunch of bugs.
And I thank my teacher for knocking the bad habit out of me. So when I see global variables beyond the initialization of core objects, I take it as a bad code smell.
I'm just not buying this argument. People add Redux to projects because they intend to use it, and there's almost never a case where a piece of data is actually needed in enough places that it should be global. It's certainly not common enough to justify importing a whole framework for managing global state.
Could you explain what you mean by this?
My intuition here is that what you're saying is true in a trivial sense, but that most state CAN BE (and should be) local if you're breaking your programs into reasonable building blocks.
All that information exists only once and is needed in many parts.
I think many people could argue that this concept itself is a bit misguided. There are plenty of application patterns and architectures that help you move stateful data around your app and translate it for the components who need to consume it or change it, while hiding it from components in the tree which do not interact with or care about the state.
If you have many different components in your app that all need access to the same state data, and they exist at different levels in the hierarchy, maybe you need to re-think your component structure rather than figure out a way to inject mutable global state everywhere?
First question (show user data in nav bar, user details page, and "comment thread"):
User profile page is obviously showing full user details, so you're going to want to have a separate endpoint for "getUserDetailsByUserId(int userId)" or however you want to name it.
What data are you showing in your nav bar? Is this just a user-avatar image link/button? You would rarely want to display anything other than an avatar and/or username in a navigation bar. You could maybe talk about a navigation drawer instead, where you have more simple details like maybe email address and phone number or something.
What about the "comment thread"? Are you showing anything more than an avatar and/or username? This sounds like the same use-case as the nav bar... how would the display differ here?
If you have use-cases for anything more than your basic "user avatar" and "full user profile," (and maaaybe "user basic info," for a "nav drawer" or something, although those are being phased out of most current design standards) then I would seriously urge you to re-think your app design. That polish of showing the user info in a slightly different slice on some specific page is almost never going to pay off for your company, imo.
Anyways, if you want to be naive about all of this, you just save the userId (and token or whatever, but that should be in your http middleware) after a new login session, and then you request separate "getUserNavBarInfo(userId)", "getUserDetails(userId)", and "getUserCommentInfo(userId)" and load the results of those into your UI views on-demand. If you need to progressively tune/cache your API, you do this separately based on usage stats for each of these 3 use-cases, you don't start with directly caching "Users" and adding the business logic client-side to translate into the 3 use-cases.
If you want to do a more thorough dependency-injection "user component," then this might vary depending on your DI framework, but the high-level idea would always just be to create a user module with all the info needed for a given user, wire it up to reload the module in your DI graph on new login and on-success of any request to update user info. You could have both your "login" and "update user" endpoints both return the full set of data needed for the "user module" on success. Then you need to set up your DI injection component scoping so that your user info injection component is dumped on user logout and settings change, and is recreated to do it's injections with the new/updated user module each time. A singleton DI "user component" that you must choose to inject into any UI component that wants to show user information seems much more constrained in complexity to me than just having the ability to arbitrarily pull out various bits of user info from a global redux state, at any place in your app.
Second question (single source of truth, increases or decreases complexity?):
I think this question is orthogonal to the capabilities Redux (and similar) provides. Redux has nothing to do with outright increases or decreases in app complexity, it is simply a tool that enables various (opinionated) architecture patterns. If you have a separate, domain-specific, well-defined event+data architecture that you are sticking to very strictly to keep a handle on complexity, then Redux can (imo) reduce boilerplate and allow very elegant interactions between various data/biz logic/UI components of your application, all without any "tight coupling". It's really just a message-bus pattern at some level. This sort of thing has been around longer than the concept of software itself, there are hardware components literally called "buses" that basically do the exact same thing (ship events and data around to various interested parties without requiring a direct circuit connection). The danger is that it opens up a TON of pathways to insane complexity and totally unintuitive event & data flows, because it kind of allows you to arbitrarily add "side effects" to basically anything in your application. VERY easy to foot-gun yourself here if you start getting lazy and just wiring up random shit instead of following strict event/data patterns and keeping the "feature code" as simple as possible (e.g. with a strict facade for writing new pages).
This might rustle some feathers on here, but I wish people would think of React/Redux sort of as "framework primitives" that are SUPER useful for constructing your own domain-specific client-app frameworks, but somewhat dangerous to open up directly to front-line product code written by junior devs. I would much rather see a few senior devs use React/Redux to construct a tightly-defined domain framework that exposes a simple facade to junior devs to construct new pages/routes/behaviors in your FE application. I think using these tools like this would greatly reduce the dreaded "React learning curve" of 3-4 months that everyone talks about. That could be done over time if you put in the work up front to build an internal facade pattern around your core React/Redux app architecture. Then junior devs could choose to dig into things behind the scenes as they go, instead of being forced to front-load the entire React/Redux knowledge stack up front right when they walk in the door.
Also, "state" does not necessarily imply "mutable". (Redux encourages an immutable state approach where good reducers don't mutate state in place, but instead build new state from old, though Redux is not strict in enforcing this best practice. For Redux the "current state" is "file handle" that simply updates from one object to the next, not all that different from stdin/stdout/stderr or even somewhat to static configuration that is somewhat updateable without restarting the application.)
> Also, "state" does not necessarily imply "mutable".
Okay, sure. I'd rather not get into a semantic argument about the meaning of the word "state". Suffice it to say that I'm talking about mutable global state.
I think these debates about global variables or state could be much more specific, though.
For example, we tend to realize that letting any component access a `require('./store').currentUser` singleton may make testing harder, but this global dependency still exists when, say, it's distributed to all components through the React context. I reckon most people are thinking of the singleton example when they lambast Redux but it certainly doesn't have to be that way.
Many people just use setState.
Since my last Redux project in 2015, I did many React and React-Native projects and not one of them used Redux.
I only did green-field projects where I was the only front-end dev.
If you don't use redux, then keeping state synced with a server side rendered application because nearly impossible.
Could you explain what you mean by "server side rendering state"?
The client receives this initial state and uses it with the html to glue together a working UI on the client.
This removes the massive JS bloat into raw html and split JS for each component.
Instead of sending 3MB of JS, you send only the JS needed to render the current page... and send additional JS as a user navigates through your website.
Here's an example with Create React App: https://github.com/cereallarceny/cra-ssr
Gonna have to quantify that. Cause I imagine if you did, you'd redact that statement. Redux is a very straightforwards and SMALL library that handles global state.
Its concept can be difficult to follow at first which may cause some to think it way more complex under the hood than it really is.
Redux is small.
> the latency cost of loading small parts as you go
The components are static JS split into files.
Pop those into a CDN and you get the best of both worlds.
Fair point. But could it be possible that there are lots of people doing React that never went to college and never wrote Python or C programs?
With that approach, you really wanted to keep every component self-contained. That meant a lot of bookkeeping with passing events back and forth between parent and child widgets, but while it was a lot of typing, it was all fairly easy, and if I wanted to change how an event was handled, it usually only involved a component and MAYBE the components immediately above or below it in the component tree. Data passed further than that was generally wrapped in objects and therefore didn't require code changes.
Then introduce Redux and similar frameworks, and I totally lost interest. It's as if the entire React community forgot all the lessons they learned writing Python and C programs in college and went back to creating global state. What in the actual hell? I wrote a good amount of code in Redux, too--I worked on teams that used it--so this wasn't just pre-judgment. These frameworks add a huge amount of hidden global complexity just to avoid having to pass parameters to components, which is the simplest thing to do.
And once you're doing that, you HAVE TO write an SPA, because any part of the page you want to be interactive gets pulled into Redux and your JS eventually eats the whole page. It's an infection.
When writing JavaScript now, I sometimes end up rolling my own version of the component style. It's an effective design pattern for UI widgets. But it hasn't been worth it to use React for a while now because the tooling and tutorials are all too conflated with Redux and similarly awful tools. And on a team, there's always the worry that someone on your team will infect your code with Redux and it will grow and consume your entire site. It's a real shame.