Hacker News new | past | comments | ask | show | jobs | submit login
Scheduling in React (philippspiess.com)
247 points by tosh 46 days ago | hide | past | web | favorite | 104 comments



Dan from React team checking in, just wanted to post a few extra thoughts about this!

https://twitter.com/dan_abramov/status/1103768273064259590


This is really interesting work. Looking forward to seeing how this plays out. Someone below mentioned that it won’t play nicely with mobx - can you point to more details about the limitions that we will run into?

Also just wanted to say a quick thank you to the whole React team for the work you’re doing. After some initial scepticism about the necessity of hooks, we’ve been really enjoying the mental model they bring to implementing more interesting behaviours in our components.


We used this technique in the IE6 days (with settimeout nonetheless). We had a very big list that was being filtered as the user typed, and we would perform 10 dom operations (they were all creating and appending elements to the dom), then timeout for 15ms, then 10 more operations, etc. If the user changed the text, we would clear the most recent timeout which would stop the chain, it was very clean actually. I suppose with requestAnimationFrame it's a bit nastier as you 'd need like a __dirtyRequestStop boolean check to break the scheduling, but probably much more elegant performance wise.

The concept behind it, is that you give "air to the UI thread to breath". Basically instead of locking it for 100ms you lock it for less at the time, which is perceivable as just a delay, rather that the window completely freezing. Users would see the list progressively be created sometimes which was cool too, gave it a feeling of "this is doing some heavy work behind the scenes" without really doing much.

documentFragment also was used for constructing the elements before appending the fragment to the main element :) I am wondering if that technique has any merit today, since it really helped back then.

What is old will be new again :)


Oh, it goes back quite a bit further than that :)

https://blog.codinghorror.com/is-doevents-evil/


Hah, of course, spoke too soon! And it's by Jeff Atwood! I got the idea back then from some game devs who used a variation of this technique (though theirs was a queue mechanism for prioritizing disk reads to speed up loading of important assets and defer the rest in the old magnetic days). Anyway, congrats to the react team, really cool.

I am wondering if they plan to abstract it completely so that everyone uses it without even realizing it.


A lot of gamedev networking is like this too. Half making sure your game model works in a latent environment/can be extrapolated. The other half is clever tricks(particle systems! sound effects!) to make the player think something is happening when you're just waiting to reconverge the state with an authorative response from the server.


It's not a new technique. The innovation in React is allowing the framework to pause render on any component boundary, and detect and redo any stale renders when inputs change.


Another important part is that’s React doesn’t commit partial results until the whole update is done. So you don’t see “half of an update”. That’s the benefit of doing it inside the library.

We also reuse the same scheduling architecture for waiting for network and other IO. You can watch our latest talks on Concurrent Mode that touch on this here:

https://reactjs.org/blog/2018/11/13/react-conf-recap.html


It's already popping up again in some other places! (For what I assume will be until browsers get an actual scheduling api)

https://www.youtube.com/watch?v=ypPRdtjGooc


As users, don't you get mad when you type "mustang" in an input and it displays "msutgna" because of some badly handled onChange event? We are overriding so much of the browser's default behavior that we need to reimplement its basic features.


Yes. I wish native browser inputs would step out of 1994, but at the same time my preferred answer to this case is simply using an uncontrolled input rather than piling more layers of complexity on top of it.


I've never experienced that. Mind sharing an example?


That would happen when, for example, you're saving the value of the text input in redux, and the input is then updated with that value on change, although it's also updated by the user typing and the thread is blocked at the same time, thus we have a race condition.

That said — it only happens when one over-engineers stuff. Make sure to have a single source of truth, and that will be avoided.


The best approach is to always updated the input synchronously in React (in the same tick when the event is handled). If you do it in another tick, you will always have to handle race conditions. This is a problem for all input elements though, not specific to React or even the Web.


The last time I experienced it I was using search bar on AliExpress.com's mobile website


I have already encounter this behaviour, but I do not understand it. What is the mistake to avoid ?


That probably not just input, but content editable. Unfortunately this feature is really very hard to support well and there are still bugs in all editors.


I feel that this low-level performance stuff should exist in browser-land and not in framework-land. Kudos to React for finding a good implementation though


The Chrome dev team is working to create a scheduling API in the browser: https://github.com/spanicker/main-thread-scheduling


Yep, and React is actively collaborating with them.


Or maybe not even in the browser but in the OS?

It is interesting how we build a new layer on top, and then notice that we need to reproduce all the functionality of the lower levels.

Maybe Open Implementation wasn't such a bad idea?


Yes it's highly unsatisfactory that frameworks are implementing "threads" (i.e., fibers) now, while the OS already provides native threads.

Perhaps it would not have been so bad if they just kept the interface the same ...


We’ll end up with threads in JS one day.




Hopefully without a "Global Interpreter Lock".


I would prefer that they implement it in the same way go has with channels.


You can significantly improve on the baseline performance simply by doing:

  handleChange = event => {
    const value = event.target.value;
    this.setState({ inputValue: value });

    setTimeout(() => {
      this.props.onChange(value);
    }, 1);

    setTimeout(() => {
      sendAnalyticsPing(value);
    }, 1);
  };
And wrapping mining function inside Name():

  setTimeout(() => {
    miningBitcoin(2);
  }, 1);
In fact, to me this pretty much seems as fast as their fancy solution. But what do I know, I'm just an idiot full-stack developer who doesn't use JS frameworks.


How does this "improve" at all?

In fact, that example will eliminate any chance React might have of batching multiple updates into a single render pass.

React wraps all event handlers in a call to `unstable_batchedUpdates()`. All updates that are queued in that event handler will be batched together.

Splitting updates into multiple ticks will keep them from being batched.


Try it instead of theorizing:

https://codesandbox.io/s/07293kpnn

The UI is nearly as responsive as their improved example.

I'm not a React expert, but I do know that browsers defer timeouts until they have time to process them. This can be used to take heavy-hitting functions out of events that block UI interactions. It can also be used for self-throttling recursive IIFEs.


If you delay the artificial slowness that I added in the Text component after the rendering, it sure will be faster. The point is to simulate an expensive component tree with this.

If we keep that in (https://codesandbox.io/s/93yv4j129p), my previous comment holds true: https://news.ycombinator.com/item?id=19332577

Here is by the way a version with all artificial slowness removed: https://codesandbox.io/s/9859x0wm9p You can see that this example does not need any optimizations out of the box. This is why I added the slowness to simulate a more complex app.


The important part btw is that in Concurrent Mode we can interrupt a render in the middle if user does an interaction, and handle that interaction first. Without blocking the thread.

This is something setTimeout is incapable of helping with. Because even if you delay the work, at some point it’s still gonna block.

In either case this example is very simplified and doesn’t illustrate the subtle difference as much. We’ll prepare our own examples when the features are stable.


>in Concurrent Mode we can interrupt a render in the middle if user does an interaction

Can you interrupt an arbitrary user-written function that's called inside the rendering stack?


No, that's not possible in JavaScript. The important part is that React components are lazily evaluated[1] and are not rendered as a stack. So React can render a few components and then pause for higher priority work.

As a user of the framework, you don't need to care about this though. So your mental model can be that of an interruptable function.

[1]: https://overreacted.io/react-as-a-ui-runtime/#lazy-evaluatio...


I think to interrupt a render means interrupt the process of reconciliation. you don't have to response the user input until the difference of whole React element has been found. https://overreacted.io/react-as-a-ui-runtime/#consistency


And I would say I reasonably qualify as a "React expert", and was having a discussion on this topic yesterday:

https://twitter.com/acemarke/status/1103373148169347072

There's a lot of nuance involved here. Moving some behavior outside the current tick may speed certain things up. Keeping multiple React updates in the same tick may _also_ speed other things up. It depends on what work is being done in these additional callbacks, and where/when they're being executed.

In this specific case: `this.props.onChange()` calls the parent component's `setState()`. That should ideally be kept in the same tick as `this.setState()`, so they can be batched.

`sendAnalyticsPing()` does _not_ cause a React update. That should indeed be moved out to a separate tick, so that it doesn't slow down this update.


It feels a bit faster, yes. The reason is that the input value is updated earlier.

The problem is, that the UI is still unresponsive when the timeouts fire which you feel if you type fast or look at the animation/fps meter.

Instead of a setTimeout, you could also debounce the two expensive updates (as I've noted in the post). This would at least make sure that they are batched so if you type fast you only need to re-render the list once.


In addition to everyone else's comments, there's another big problem: browsers treat setTimeout delays as very loose suggestions.

If you're scrolling when one of these timeouts is set, or one starts and then you start scrolling, it's possible for the browser to delay the callback for a super noticeable amount of time, on the order of 3–10 seconds (about a thousand times slower than you intended).


I understand the need but this adds serious complexity and wouldn't recommend that this becomes the standard for UX in React. A user is often ok with a delay and it may not worth the tech debt. That said, a very well written article.


A user is often ok with a delay...

Every user group focus test I've ever done on an app with a janky UI has brought it up as a significant "The app is so slow!" issue. On the things I build users hate UI delays.


This is a tough one. If asked, most everyone will complain about delays. Just like, if asked, most people will complain about the color choice. Doesn't mean it is the best place to focus on at first.

In areas where the latency has been solved, don't make the options worse for the user. There are bare minimums, but they are usually hard to mess up. Such that, if you are bringing something new to the table, focus on that first.


Most of the complexity is inside of React, not in the app. Your components look pretty much the same — except that you have more control over scheduling when you need to.


React trades simplicity of certain things such as dom manipulation, and makes it difficult to fetch external (ie noncompiled) html and include it as react dom nodes, and ad serving is pretty difficult too because most ads use document.write and this paradigm doesn't work with react, as well as single page applications and the rendering of contextual advertising requires server-side rendering too.

State management solutions like redux; nobody i know or have seen does it exactly the same, its really tricky to use and takes a long time to get the hang of.


I have always considered React one of the frameworks that allows you to interop with regular HTML and JavaScript the easiest. The reason being that you can easily access DOM nodes and everything is run in JavaScript anyway. This is a lot more complicated in languages where you have a template language in my own experience.

That said, document.write is kind of special since it requires JavaScript to run while the HTML document is still open (so before it fires the loaded event). There is a reason this API is barely used today and mostly superset by appendChild.


> it may not worth the tech debt

Management is complaining that our website is less responsive than our competition's ...


Responsive as in ms delay from click to action or responsive as in mobile/tablet visual scaling?


Keep in mind that the APIs are still WIP. I choose an example with lots of APIs on purpose.

There's a lot that can be inferred by e.g. looking at the event type or understanding semantics. I expect this to be better when the features near completion.


I think React is becoming a browser in a browser.


There's definitely some very cool work here... I do think that it's important to remember that the simplest solution is usually the best one to start with. I typically start with redux and redux-thunks with react apps, mostly because I will nearly always hit a point in complexity where it is worth separating state management from the main UI, and thunks is the simplest extension to support async interactions to a server.

From there, debounce or concurrent interactions are probably an easier stopgap first. At least until these APIs are distilled a bit, and abstractions make them easier to work with.

This can all be summed up with: YAGNI and "Don't prematurely optimize" I've only had a handful of cases where I had to make things more difficult because of behavioral issues in browser applications.


>> simplest solution is usually the best one to start with. I typically start with redux

?? Redux is definitely not the simplest solution :) I’d argue that starting with local state (which is what hooks seems to be headed towards), then profiling, testing, and adding small amounts of debouncing code where appropriate is more aligned with your sentiment of “don’t prematurely optimize” vs starting with redux.


Hi, I'm a Redux maintainer. A few thoughts.

There's a couple ways to approach building out an app:

- Start as small and as simple as possible, and add more pieces down the road once you need them

- Determine up-front if you think you'll need various pieces, and get that infrastructure in place at the start .

Either of those is a reasonable way to tackle things.

As a related note, I'll put in a pitch for our new Redux Starter Kit package. 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:

https://redux-starter-kit.js.org


Man, why does every single thread about anything remotely related to react devolve into a flamewar about redux? I wish we could all move on from this tired old debate. Everyone is free to use or not to use redux. If your team uses redux and you hate it, that is not redux's fault.


I read the Redux site for the first time recently and I realised that it is a transliteration of the Elm architecture (TEA). TEA is probably easier to understand first as it fits more nicely into its native Elm than Redux does into JS.

I think Redux is quite simple conceptually, but if you just look at the code without the concept and reason behind it, it’ll look like complexity for the sake of it.


More often than not you should have a general feel for how complex your app will grow in the medium / near term. If it's going to get beyond "toy" you might as well take the plunge with Redux up front. Starting your design with it is much easier than trying to shoe-horn it in later, even if the cost at the outset feels heavy.

If you're fairly confident your app won't get too over the top, then by all means go the easy route!


No, it really isn't... but it's a level of complexity that once inherited doesn't get much more complicated as you add more features. It's also imho one of the best options for separating state management.

In general, prop drilling is simpler to start with, but quickly turns into spaghetti... you can use the new context options, but they aren't much easier than using Redux at that point, and can become much more complicated.


Then you end up with data fetches being tied to the rendering lifecycle in a way that makes modifying the mechanism painful


You'll eventually need data in the global state in anything but a toy app.


Putting everything in global state gets really hard to maintain above a medium-sized app.


But... It's not a choice of one or the other, both global and local must be used appropriately.

Also the shape of your global state plays a big role in its maintainability and redux provides good tools and documentation to achieve that.


You can still slice state into separate reducers and action creators. I use a feature oriented directory structure, so that a given feature may have components/controls, action creators and/or reducers, but not necessarily all of the above.

It sounds a little convoluted, but in practice it's made it easier for developers coming in to discover/predict where things are.


Yep. I've settled on a "feature folder" approach myself. When we finally get around to revamping the Redux docs content, I plan on having a "Style Guide" page similar to the one in the Vue docs, which would offer opinionated suggestions with an indication of how important each one is. One of the items I definitely plan to include in there is a suggestion to use feature folders. I also want to reorganize our example apps based on that structure as well.

See this issue for details on the docs rewrite:

https://github.com/reduxjs/redux/issues/3313


I too prefer feature folders. I wish it was the example most used...as it is we have a convention that has no reason other than "this how I first saw it done".


I generally agree but actually the new hooks allow you to pass certain values down to all children automatically, so I think just hoisting this logic to the highest point that makes sense has this covered to some degree.


I think you're talking about context, which isn't specific to hooks. Hooks give functional components features that only existed for classes.

First of all, context is slow, at least currently. Secondly, it's just a vehicle for delivering deep updates. It should be used when local state is not enough, but it's only an intermediate step before needing redux-like state Management and all the convenience it provides.


There's also a big piece that people who see useContext/useReducer as a replacement for Redux usually miss: server side rendering.

The Redux store lives outside of the React component tree and has a getState() method to capture the state of the store and deliver in the SSR payload. With useContext/useReducer, the state lives in the lifecycle of a top-level component. Unless you build something equivalent to Redux's store anyway, you're not going to be able to easily get a snapshot of its state out of that component. Unless, I suppose, that component itself knows to put its own payload in a <script> tag in its render() on the server only (in which case you'd still get a hydration mismatch).


I would argue that you should indeed hoist your data to the highest point, but definitely not your logic. Otherwise you end up with a big mega-component at the top level that contains fragments of business logic for many unrelated features, and coupling your business logic to your view component hierarchy is not a good idea. Changing your page layout should never break your application. I don't advocate any particular solution to this problem but I do believe that you need some kind of architecture that allows you to decouple your UI code from your application code.


The new hooks are a great way to use redux, not a reason not to use it. A “useStore” hook is much easier to understand than react-redux's connect.


I don't think redux-thunk is the simplest solution. There's really no need to add anything to redux to support asynchronous code. You can just pass the dispatch function around.

For example:

  async function getArticle(id, dispatch) {
    try {
      const res = await fetch(`/articles/${id}`);
      const text = await res.text();

      dispatch({ type: 'GET_ARTICLE_SUCCESS', id, text });
    } catch (err) {
      dispatch({ type: 'GET_ARTICLE_FAILURE', id, err });
    }
  }
In my opinion all this middleware business (thunk, saga, etc.) is trying to make redux do jobs it isn't supposed to be doing.


Yes, it's true you can do that. However, it's not what we recommend.

I specifically addressed the reasons why we recommend using middleware in the "Side Effects" section of my "Redux Fundamentals" workshop slides [0] [1].

The Redux FAQ entry on "Why do we need things like middleware for async behavior?" [2] also addresses this. In particular, I recommend reading Dan Abramov's answers on Stack Overflow on this top [3] [4]

As always, it's up to you how you choose to write your code, but there's plenty of good reasons why middleware is our recommended approach.

[0] https://blog.isquaredsoftware.com/2018/06/redux-fundamentals...

[1] https://blog.isquaredsoftware.com/presentations/workshops/re...

[2] https://redux.js.org/faq/actions#how-can-i-represent-side-ef...

[3] http://stackoverflow.com/questions/34570758/why-do-we-need-m...

[4] http://stackoverflow.com/questions/35411423/how-to-dispatch-...


Yes, I'm familiar with the arguments against it, but my experience using Redux has led me to strongly disagree with them. I don't think the proposed benefits have much value in actual practice, and limiting the use of dispatch to plain objects makes state logic easier to understand in my opinion.

Also, I think my primary issue with redux-thunk isn't that you dispatch a non-object but that the getState argument encourages async operations which are dependent on the current store state, possibly its current state at multiple different points of time. Personally I think that as much as possible async operations should be written to use only parameters as input and dispatch actions as output.

Plus I use TypeScript where things like this getState argument are really annoying for strong typing. You need to import the type of your store or store state in every file that uses a thunk.

I also personally think that the extra ceremony applied to Redux is a contributor to the difficulty new developers have understanding it, because they believe that Redux does more than it actually does.


I understand most of your concerns, and it seems like we'll have to agree to disagree to some extent.

FWIW, I specifically addressed several concerns regarding use of `getState` in my post "Idiomatic Redux: Thoughts on Thunks, Sagas, Abstraction, and Reusability" [0].

I agree that trying to fully capture the potentially dynamic behavior with static types can be painfully difficult. I don't actually use TS myself, yet, but we've definitely had lots of issues pop up related to this (such as [1] ), and I think I get the general issues involved. Unfortunately, I don't have any real suggestions to offer on this front, both because my TS knowledge is limited to "declare types for function params and object fields", and because I'm not sure there _are_ ways around that.

Having said that, our new Redux Starter Kit package [2] is specifically intended to help simplify a number of common Redux use cases, and I'd encourage anyone using Redux to try it out.

Long-term, we plan to revamp the Redux docs content [3], and I hope to improve a lot of the teaching workflow. I also hope to make RSK the "default" way to use Redux for most people.

[0] https://blog.isquaredsoftware.com/2017/01/idiomatic-redux-th...

[1] https://github.com/reduxjs/redux-thunk/issues/231

[2] https://redux-starter-kit.js.org

[3] https://github.com/reduxjs/redux/issues/3313


I do think we need to agree to disagree. That being said according to the README for the Redux Starter Kit these are listed as problems it wants to solve:

• "Configuring a Redux store is too complicated"

• "I have to add a lot of packages to get Redux to do anything useful"

• "Redux requires too much boilerplate code"

I can't help but point out that all of these concerns can also be solved by not using any extra libraries and just doing what I suggested.

Configuration: createStore()

Lot of packages: Not needed, just pass dispatch around.

Boilerplate code: Pretty much just combineReducers calls.


I'd encourage you to read through the RSK docs further to see what all it actually does, then :)


Sorry, I know you're heavily involved in this ecosystem, I just don't really see the need for any of this. create-react-app is mostly about webpack, isn't it? Redux works fine on its own.


FWIW, I'm a Redux maintainer, and I built this specifically in response to how I've seen people _want_ to use Redux, and the concerns they've raised about using Redux. [0]

Almost everyone uses `redux-thunk` [1]. Adding that to a plain Redux store takes a few steps. Adding middleware _and_ setting up the Redux DevTools Extension adds another couple steps [2] So, RSK's `configureStore()` does that by default [3].

Accidental mutation is the #1 mistake folks make when writing Redux apps [4]. So, `configureStore()` adds a middleware by default that warns about that mistake [5].

Writing immutable update logic can be difficult, and especially painful if the updates are nested [6]. Also, many folks don't want to use switch statements for some reason [7]. So, we ship a `createReducer()` utility that lets you write "mutative" immutable updates, and define the reducers as a lookup table [8].

Many people don't like writing action types and action creators by hand. So, RSK includes a `createSlice` utility that generates those automatically. [9]

None of these are incredibly ground-breaking. There's lots of existing Redux addons that do similar things. But, we're including all these utilities in an "official" package, and recommending that folks use it.

No one's being forced to use RSK. But, I can say that just about everyone who's seen this has said something similar to "this looks awesome, I can't wait to use this!".

[0] https://github.com/reduxjs/redux/issues/2295

[1] https://blog.isquaredsoftware.com/presentations/2017-09-migh...

[2] https://redux.js.org/recipes/configuring-your-store#integrat...

[3] https://redux-starter-kit.js.org/api/configureStore

[4] https://redux.js.org/faq/react-redux#why-isnt-my-component-r...

[5] https://redux-starter-kit.js.org/api/getDefaultMiddleware

[6] https://redux.js.org/recipes/structuring-reducers/immutable-...

[7] https://blog.isquaredsoftware.com/2017/05/idiomatic-redux-ta...

[8] https://redux-starter-kit.js.org/api/createReducer

[9] https://redux-starter-kit.js.org/api/createSlice


That was a very defensive response.

Look it's excellent for you that many people want to use your work and think it looks awesome. I am just not one of those people, for reasons developed over years of using Redux professionally.

It seems like you think I am just missing something, and that's why I don't agree with you. You should really be more open to the idea that others might think very differently from you and the people you surround yourself with, even after fully considering your ideas.

I also knew you were a Redux contributor when I first responded. I was wondering how long I could disagree with you before you pulled rank.


I've been down this path with acemarke before, about redux-thunk even. All of his commits on Redux are documentation changes, and he feels the need to spam every React thread on this site with comments that start with "I'm a Redux maintainer". In-fact if you google "Redux maintainer" this guy's posts are the first thing that shows up.

Also, when React 16 came out with the new Context API, acemarke made a terrible blog post called "Is Redux Dead?", which had links to some sort of paid Redux training, and linked it everywhere without disclosing that fact. He regularly links said posts from his blog (which I assume have the same links to paid training) under the guise of helping beginners, and I'm really not sold that they actually accomplish that at all. In-fact quite the opposite.

It all seems pretty shifty and dishonest.



What you're describing isn't much different than the use of thunks generally...

    export const getArticle => event => async (dispatch, getState) => {
      const id = event.target.dataset.id;
      ...same as yours...
    }

    ...
      <button 
        data-id={record.id} 
        onClick={this.props.getArticle} 
    ...
The main difference is this works out of the box with connect, and can often be bound directly against a given event handler.


IME, Mobx is much simpler than Redux. /$.02


You can use Mobx and get around all the manual effort of Redux.


This seems pretty nice! Personally, for most cases, I would just fix the actual problem instead (long-running tasks) by either moving them to a Worker, batching the work or using some LOD algorithm to only run to work for the needed granularity. This would improve the UX a lot and is not something that you have to do to often anyway.


The worker approach doesn't work if you need to update lots of Dom nodes. The example being a highlighter for a search term.

While LOD would work (e.g., only update on screen names), the work featured to set that up is non trivial


This might also add some context of why we’re not going with just workers (btw we tried that): https://github.com/facebook/react/issues/7942#issuecomment-2...


AFAIK concurrent React breaks a lot of the currently existing ecosystem. For example, I think right now the current versions of redux and MobX don’t work with it. Granted, concurrent mode is not even close to out yet, but I’m curious what the migration path looks like.


I don't know the answer (or if there is a fully formed answer at this point), but I have heard the React team talk about this a bit.

Facebook internally has a MASSIVE number of components, and apparently a lot of them are using older APIs and older paradigms. Because React is ultimately by and for Facebook, they simply can't afford to make lare breaking changes, it's just not a realistic option. Everything they do has to have a good mostly-automatable path forward.

I don't know for sure how they will handle it, but I have a feeling it will be using the StrictMode stuff that they introduced a few versions ago. I'd wager they will have a way to enable concurrent react for a Component and the tree of children nodes, and will stick to the current implementation for everything that isn't compatible. This will let you enable the new features in only parts of your app that need it, or slowly migrate over to it and opt-in over time. And I believe I read somewhere that if a component runs happily in StrictMode that it's already concurrent-react ready (but please don't take that as fact!).


Yeah, ConcurrentMode is going to be entirely opt-in, per subtree, as a component: `<ConcurrentMode>`. And yes, that's the point of `<StrictMode>`: a form of runtime linting for known patterns that will cause problems in CM.


I think this is a great point made. I wonder if it makes sense to build into the core of react to have render method execute on priority levels configured for the components themselves. I agree that this adds a good deal of complexity otherwise for quicker projects.


What complexity are you referring to? If you don’t use finer grained control then your React apps work exactly the same ways as they did before. The extra control is opt-in and you don’t have to use it.


Something like this is definitely possible. For example a <div hidden> could apply all updates using a lower priority. This can be used to detect offscreen content or non-active tabs.


Isn't this some of what Fiber was supposed to take care of?


"Fiber" was the codename for the internal rewrite that was completed and delivered as React 16.0.

That rewrite has enabled all of the further functionality that's been implemented in 16.x, including the first stages of "Concurrent Mode".


Thanks for the article! Well written and clearly explained Concurrent to me. Looking forward to trying out the API myself.


Impressive, but I don't think there's much of a use case for this.

Sure that last example ran at close to 60fps, according to the meter, but did it really?

I mean, as a user I experienced no performance gains, because I was focused on that lower part of the UI and I think most users would as well.


People are highly aware of when their input is locked up. It's like trying to type commands over SSH on a bad or laggy connection: you have to keep pausing, wait for it to catch up, and ensure there are no typos or anything. Oops, need to backspace, wait, and edit that character. Oops, backed up too far.

Classic example of this is a naive markdown preview box that updates on every keypress. As the input grows, the experience becomes more intolerable until you're left typing the post in notepad and pasting it into the <textarea>.

You are only able to look past it here because of the contrived example. Though I found the demonstration intolerable and would hope, as a user of your application, that you wouldn't settle on "meh, the user won't notice/care." Especially since you don't notice it on your developer workstation and I'm stuck using it from my low powered mobile device.


Though I found the demonstration intolerable and would hope, as a user of your application, that you wouldn't settle on "meh, the user won't notice/care."

Actually I would just debounce it and add a spinner. No need to shoehorn additional operations in between, which are going be outdated in a split second anyway.


Debouncing just means the UI lock happens slightly less often. Not sure how a spinner is going to work when we're talking about synchronous updates which the article's `mineBitcoin()` is trying to simulate.

So it just depends on how much you care about UX. And consider my markdown preview example where UI lock on every Nth keypress is hardly better than every keypress.

If you don't care, then why bother with auto-updating on keypress at all? Just wait for user to press enter. Much less jarring for the user than a shoddy half-solution.


Debouncing just means the UI lock happens slightly less often.

No, it means it happens only after the user stopped typing for a while - a very different experience.

If you don't care, then why bother with auto-updating on keypress at all? Just wait for user to press enter.

Doesn't cover all the cases and you have to update eventually - after the first debounced event goes through is a good moment.


The batching effect of a debounced input is very important. Running an update for every keystroke is expensive and you only want to run it with the latest input anyway. This is why Concurrent React will make batching the default behavior.

Internally, React manages a list of updates. Whenever you trigger one, it will be added to the list but the next re-render will only start after the previous one has finished (this is of course a simplified explanation but you get the idea).

This batching gives us the best of both worlds: We start rendering as soon as we have data and don’t have “idle” time in which the UI is unresponsive (like a long debounce function) but we still batch all updates between the different renderings so that we can skip individual updates to save time.


ELI5, what's the benefit here?

Because now I see mostly the cost of having pieces of the render function shoehorned between input events just so that they can be discarded, because once the render function finishes it's grossly outdated.


Your users still want feedback _as they type_. The idea of this example though is that you want to see an update as soon as possible. If you use blur, you're also deferring the updates until the timeout is over for the first time - The timeout can only be approximated and will ultimately be more keystrokes behind as if we start rendering ASAP.

There is also the other problem with debounce that was pointed out earlier: The main thread _will be blocked_ when a long running tasks is executed, no matter _when_. You mentioned that the block would happen _after_ the user type in so it would be less noticeable but this is only an approximation. If you have animations on your website that use rAF, the block will still be very visible. Also if the user continues to type.

That said, I know that this is a contrived example and it might not be used as-such in a real world application. I also think it's not comparable to your auto-saving input example that would maybe be better off with using a debounce call. I'm sure that better examples for this use case will follow.

Edit: I can recommend Dan's talk at JSConf Iceland last year. He speaks about debounce as well: https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react-...


I found the second example to be way less janky. I want to be able to see what I type as I type it (e.g. for spelling mistakes).

Taking a second to correlate that with results I am willing to accept, but if an app is janky with basic interactions I am leaving.


I want to emphasise both of examples are intentionally janky (even though second is less so). As the article states, it literally has a for loop that doesn’t do anything to simulate an overly expensive tree (think thousand of DOM nodes).

You can see the difference between first and second approaches that way. Although both would be fast if we remove that artificial slowdown (simulating a larger app).

However I also want to note that you shouldn’t base your idea of what Concurrent Mode will feel like based on an alpha version demo. It’s work in progress and we didn’t mean to imply it’s ready to be denied at face value. https://twitter.com/dan_abramov/status/1103769293479653376


great article, thank you




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

Search: