10 ms·
The problem that you end up with past a certain scale in a component-based app without a state management framework (third party or your own) is that you're per
by pault 8y ago
The problem that you end up with past a certain scale in a component-based app without a state management framework (third party or your own) is that you're performing business logic inside view components. By moving your business logic into your state management framework and using something like redux-saga that is designed specifically for managing complex asynchronous workflows, you can extract your application code from your views and make your views pure functions of the state tree.
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.
- kokokokoko 8y ago> The problem that you end up with past a certain scale in a component-based app without a state management framework (third party or your own) is that you're performing business logic inside view components. 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.
- pault 8y ago> Very few components in a React application require state management. 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. :)
- rashkov 8y agoGreat points all around. One question though — how many apps actually need to share state across pages? Seems like mobile apps have this problem more than desktop apps, because their screens are small and so the UI must be split into pages. Whereas a large monitor can support an expansive UI that isn’t “split up”. So if you’re making a desktop app, what would you use instead of redux? Seems like your points about splitting off business logic, debugging and testing all still apply when the state is confined to a single heavily interactive screen with complex state.
- pault 8y agoIf you don't have any routing to deal with, component state is all you need. You can still move your business logic out of the view components and use lifecycle methods and event handlers to coordinate it, but the use case you are describing is the ideal case for vanilla react.
- rashkov 8y agoIf making global state accessible is all you need Redux for, then I don't see why that state shouldn't be: 1) persisted to a back-end and re-fetched on route 2) Persisted to localStorage 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.
- pault 8y ago> persisted to a back-end and re-fetched on route 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 :)