Hacker News new | past | comments | ask | show | jobs | submit login
Why is setState asynchronous? (github.com/facebook)
136 points by stablemap on Jan 25, 2018 | hide | past | favorite | 45 comments



A (presumably controversial) way to use React is to use only props never state, manage state in your own variables (possibly in a separate "controller" class) and call forceUpdate() when you have changed those variables such that the render would change.

This gives me flexibility to structure the code how I want. You can have the separate "controller" classes deal with operations (like API requests) and the "react class" mostly deals with display - it delegates actions to the controller and retrieves data from the controller. This can promote code reuse, notably certain components can share the controller class (not instance) when you have similar logic.

As a more general principle, I like react just fine as a way to express the DOM in a functional way and for its magic of figuring out just what parts of the actual DOM must be updated; but I don't really care about how React wants me to write code in other aspects, like where and how I should manage my data.


Actually, using props instead of state is a pretty common thing to do - it's how redux integrates with React [1]

[1] https://redux.js.org/docs/basics/UsageWithReact.html


Hi, I'm a Redux maintainer. One of the most frequent areas of confusion around Redux is whether you can / should put any data into React component state when you're using Redux. The answer is "yes, you can, and it's up to you what state lives where". The Redux FAQ entry at https://redux.js.org/docs/faq/OrganizingState.html#organizin... has some rules of thumb to help you decide what state should actually live in Redux.

I've certainly seen a number of people in the community who insist that you _must_ keep everything in Redux, and that you should probably only use functional components along with that. I can understand why people might want to do that, as it means there's more state in the Redux store for traceability, UI output becomes highly predictable, etc. However, I would say that approach is too dogmatic and over-opinionated. It's a valid choice if you want to go there, but not something that should be enforced across the ecosystem or taught to learners. Class components, functional components, Redux store state, and React component state are all tools with specific use cases, and throwing any of them away is unnecessarily limiting your toolbox.

(Might not have been exactly what you were asking, but given how frequently this topic comes up, I wanted to put that clarification out there.)


What i like to do is to write every component as a stateless component. This way you can write a wrapper component that handles local state, lifecycle methods etc and just pass the needed functions and variables to the dumb component or you can switch to redux. The component doesn't care it just needs to have the state altering functions as parameters and the updated state. The problem i see is when new developers start writing a huge class that is coupled with the state handling and calling this all over the place.


I use withState from recompose heavily for this reason.


That’s what I do. Even if you’ll likely never use the stateless component directly in an app, it makes automated testing and things like Storybook much simpler.


Yeah, I'd definitely argue that using Redux should be the last resort. When only a single component ever needs to care about data, using component lifecycle is much more self-contained


... but also a very bad idea in practice, as most devs eventually find out. Using the Redux store in lieu of state spreads code that could otherwise be self-contained all over your codebase. It's a good way to make a huge mess and write some very brittle code.


I would refer to acemarke's comment above. It's not gospel to have to use redux for all your state. I prefer to separate my state into tiers conceptually.

Think of the classic case showcase of redux being used with immutable structures to implement a page-wide undo easily. There's a certain amount of state that could be burried deep in components that doesn't need to be in the global redux store.

Sure, your undo might not now be an exact representation of the historical state of the page, but the state that counts is still re-created, and that's the state that you can decide to put into redux.


I’ve managed a huge React app with Redux and never came across this - I can’t imagine how your case would come up. Even though we use a single state object, the concerns of a single piece of state tend to be contained in its reducer and component only.


Do you maintain a 1:1 relationship between reduced state and components? I'm curious because your word choice seems to imply you do and I'm wondering how popular that is if so.


I'll be honest - I'm having trouble parsing what having a '1:1 relationship between reduced state and components' actually means. The most common "state" in our application is the result of a GET request (for example, you need some data from the server), and an accompanying loading/loaded/error state.

Most (presentational) components will load its needed data from a container component (through connect(), or other higher order components we developed). So for example, a given `user` object, could have its data in a chat component, or an analytics view, or a settings profile. The reducers aren't generally concerned with how the data is used, just that its available to anyone who asks.

As far as localized state goes (like a form), we took a page from reduxForm (and we use reduxForm) - if there were times we needed to use setState a lot (for example, we had a query builder and chart components), we built a higher order component (like connect), where you would pass the props you need, and that component would figure out how to query it and make it available in the redux state. Again this meant you could create a "query object" from one component, that was theoretically available to any other component (as part of the Redux tree), if they asked.

All in all, I never considered any sort of 1:1 relationship between state and components. I had state, and the components decided what state they needed and rendered themselves appropriately. This was a fairly complex SPA that had real time chat, analytics and CRM-like features, there were 100s of components.

Our codebase was far more robust from the Angular version we had rewritten it from a year ago (a component crashing didn't bring down the whole app), and the concerns for every component tended to be self-discoverable (we were able to hire interns to start hacking on it within 2-3 days, after they wrapped their head around redux). I'll admit it took me sometime to understand the benefits of Redux, and there was a lot of ugly boilerplate until we started using higher order components, however "brittle" and "messy" is the last words I'd use to describe the codebase.


From a flux/reflux perspective I'd 1-1 relationship to mean that every component had a matching store, but since Redux has a single store not sure what pattern would match.


I don't think he's implying that. A good way to think about it is mapping reducers -> features.

Reducers are composable, so you can actually end up with pretty clean code in large codebases


I find that hard to believe. Coupling state with components is exactly what makes React brittle imo.


It sounds like you don't have experience with redux and how it is used, which makes me surprised that you have such a strong opinion about it.

Redux makes available state that is required globally, like the contents of your todo list or other properties that must persist as you navigate a web app. Components will store their own state for their specific requirements.

I suggest reading up on redux use cases to find out more before dismissing it entirely.


It's not controversial at all.

What is controversial is the (incorrect) notion that developers shouldn't use state. There are some perfect use cases for state and people somehow got the idea that they shouldn't use it (as opposed to not using it as the first tool, necessarily, if props can be used).


This was exactly the motivation behind a project I wrote called react-redux-controller [1]. It has made for great separation of concerns between state management (Redux), display logic (React), and implementation of processes (the controller functions). It's completely possible to segment portions of the app such that slices of those three aspects are colocated and then composed as needed.

I don't necessarily recommend using this project specifically, as I don't devote any time to maintaining it. There's a whole next-generation of how I think it should work that I haven't gotten around to persuing. But I mention it to back the overall approach. It makes UI refactors extremely easy, as components are generally not responsible for any dependencies of their descendant subtree, with the exception of some parent-child interactions.

I haven't tried it, but I think Freactal [2] also seems to allow for this sort of separation of concerns.

[1] https://github.com/artsy/react-redux-controller

[2] https://github.com/FormidableLabs/freactal


You can also ignore React's built-in 'state' object and just store stuff directly in variables using MobX, which will force update components for you.

That said, it is best used as you describe it: a store that holds all the application state and manages API requests, and then any time it updates, any components observing this store will be forceUpdated


I agree with your 'stateless' approach for many use cases that are otherwise 'muddied' by unnecessary component state.

However, instead of calling forceUpdate(), I was under the impression that componentWillReceiveProps(props) was more efficient?

Perhaps I'm wrong on that one, though... Just that forceUpdate() is an approach that is officially encouraged against.[0]

[0] - https://reactjs.org/docs/react-component.html#forceupdate


The question I ask when deciding between component state (via setState) or application state (via hoisting / redux / mobx) is simply whether the state is relevant to the application as a whole.

One example is the text I’m currently typing - that would only be relevant at a component level, maybe including an adjoining word counter. Once I hit reply, though, it becomes part of the application state.

An exception would be if I was continuously storing what was typed for offline use - in that case it’s always application state.


Then you might like Undux - that's exactly how it works.

https://github.com/bcherny/undux


You are describing redux.


They really should have just named it `queueStateChangeRequest` instead of `setState`, since it does the former and not the latter.


The title should probably mention the technology the question is referring to. This isn't a javascript forum...


The linked comment is very meaningful and comprehensible even if you're not a web developer (which I'm not). The only word in the title that may not be immediately meaningful is "setState", but the rest is still a huge draw for anyone who has had to deal with async code.


you’re arguing nonsense just for the sake of arguing. if someone hasn’t used react before then the title is meaningless.


I haven't used React before and the title definitely grabbed my attention. The linked comment was very informative to me, even though I'm not a UI dev.


The url's actually pretty explicit ! (github.com/facebook/react).


JS is a mess.

I think this blog post highlights the frustrating viral nature that is async functions:

http://journal.stuffwithstuff.com/2015/02/01/what-color-is-y...


JS is a mess, but async messes aren't JS's fault. Async is hard, but also everything just has to be async -- there's no way around this really. If you write sync code and later you need it to be async, then you're in for a full rewrite. If you write async code and you later need a sync version then you just add a wrapper that waits for completion.

In a world of fast CPUs, slow memory, distributed computing (whether client/server or peer-to-peer), remote function as a service, ..., asynchrony is a hard requirement.

The GUI event model has been with us since... even before the days of X11. Asynchrony in the UI is and long long has been a hard requirement. Users hate synchronous UIs, and rightly so.

Asynchrony is also difficult. But it's not like sync + threads is a panacea -- it's not because threads are way too heavy-duty. Better to deal with the pain than to deny the need.


>If you write sync code and later you need it to be async, then you're in for a full rewrite

If you just want it not to block using the page then you can just chain the sync code into a bunch of async callbacks. For actually parallelizing tasks that weren't before, it can be harder, but in my experience still not a full rewrite. It is much harder for larger applications in other contexts like the migration of Firefox to Quantum, though, yes.


Rubbish! You could make the same argument for parallelising, abstraction, logging, and just about every other feature. But then you’d never build anything because there are infinite possible features you could include.

Build only what you need today, and prepare for what your employer says you will need tomorrow. Anything else is like saying everybody should buy a jeep in case they move to the countryside.

Async brings its own costs. Unless your employer benefits, you are costing them money by giving them features they don’t need.


You have to think at least a bit ahead. Better to write thread-safe async code from day 1 than to rewrite later.


YAGNI - https://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it

I’ve worked with plenty of valuable apps not using async. If the requirements change such that async becomes the best way to solve the problem, that’s something the business would decide at that point.


Sometimes you can think ahead and see the need coming -- or not coming -- and act accordingly.

Experience is helpful here.

When I have to write C I try to at least write thread-safe C close to something I could expose as a library. I've had too many cases where I or someone else wrote code they NEVER thought would be needed as a library, therefore took all sorts of liberties, and then suddenly there was a need for a library to do what said code was doing. Oops. I've run into fewer instances of sync code that turns out should really have been async and where adding threads isn't good enough, but I've still run into some.

This isn't about functionality -- this is about scalability. When you know you need to scale, you have to think ahead about it.


A lot of projects are little more than crud with a few complicated business rules. Use a framework with a good caching layer, and a lot of the time the employer is very happy with the outcome. It’s cheap to build, maintain, and host.

But then along comes a developer who says “we must build this app with async so we can scale”. Suddenly the junior devs build an absolute mess because they don’t know how to work effectively with async. So a few seniors get involved. Then the project runs out of cash.

Sure, some projects benefit from async and some fundamentally require it. But not all of them, I doubt even the vast majority of them.

You only need to scale when you’re successful, and most applications are not successful.


"JS is a mess, but async messes aren't JS's fault"

Still though, JS is a mess.


Oh, no doubt!


> JS is a mess.

The linked blog post doesn't seem to agree with you. He goes on to talk about Promises and async/await—both features of modern javascript—which he describes as "nice":

> Async-await is nice, which is why we’re adding it to Dart

He does finish by talking about Go's concurrency model, which he describes as being more "beautiful", but I still wouldn't confuse "it's nice" for "its a mess".

Also, the line about Java doing it better seems to be a joke.


That's a good article. And yes, async is totally viral. Once you start using it, everything becomes async if you like it or not. I definitely need to study Go and goroutines. They seem to have a better approach.


There is no good approach to avoiding asynchrony. There's been nothing new in this department for decades. You have these choices:

     - async, continuation passing style code
     - async with delineated continuations to make snippets look synchronous which aren't actually
     - cooperative multi-tasking (co-routines with async I/O under the covers)
     - threading (preemtible multitasking)
Each one of this choices has its problems, and all can be characterized as where on the spectrum of explicit vs. implicit state keeping you want to be. For example, with threads and co-routines you keep a lot of implicit state in the call stack, whereas with async CPS you make all of the state fully explicit. Async CPS code is the least problematic once you wrap your mind around it, but it does have "mind warp" as a hard requirement.

There are times, of course, when your code is very serial, and then you really do benefit from co-routines/threads. For example, PuTTY's implementation of the SSH version exchange, key exchange, and user authentication protocols, is one HUGE C function that is actually running as a co-routine which cooperates with the UI side, with async I/O under the covers. The PuTTY approach is wonderful for that particular case: those parts of the SSH protocols are extremely serial, and the resulting serial-looking code is very very easy on the eyes (so much so that it makes up for the enormity of the function that implements it!). So it pays to be able to reach for co-routines sometimes, but not always, and I'd say not even most of the time.

Programmers today simply have to be prepared to deal with everything-is-async codebases, and they should be prepared to create codebases where everything is async from day zero.


I believe the "programmers should be prepared to deal with async codebases" is purely an engineering issue and async is a question of providing the necessary syntactic tools in order for them to achieve that.

There's nothing special about "async" per se. What about people writing OS code in the 90s? They didn't even hear about async and the "sync" was invented for the sake of easier control over the time shared between processors (remember all of our implementations of processes in OSs are CPS-style, where they just give up the control over their stack if run for more than X ms).

Other thing I do recently consider a lot is asynchronicity between machines: 1) receive a request 2) send a database request 3) return an error if database request had timed out ...) etc. Having a language that spans several systems, enveloping time, execution redundancy contraints and ensuring data passed is not garbage is something that would be novel.


"syntactic tools" -> implicit state.

There's only so much the compiler can do for you. At some point you have to bear some of the cognitive load of state compression (which is really all async is about).

Multi-processing was great, but it doesn't scale to today's world, and neither does multi-threading. It doesn't matter in the least that those two technologies allow the programmer to write serial-looking code that does not execute serially. Those technologies cannot compress program state embodied in the call stacks and heap, therefore they do not scale (because they consume too much memory, have worse cache footprints and higher resident set size, involve heavy-duty context switching, and so on).

In terms of actual novelty in this space in computer science, I don't believe there's been anything new since the 80s. Everything that seems to be new is an old idea rediscovered, or a new take on an old idea, so either way not new at all.

CPS -> least implicit state.

Process -> most implicit state.

In between those two there are a few options, but nothing is a panacea, and ultimately CPS is an option you have to be prepared to use.

Less implicit state -> explicit state, which you can compress well because you understand its semantics.

Less state -> less load for the same work -> more work can be done.

In some contexts (e.g., the UI) this is a very big deal, which is why GUIs are async.


JS is a mess because React is using a concept similar to double buffering to prevent bad rendering? That's all this is, they are batching updates to the DOM before reconciliation to reduce redraws.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: