Scheduling in React
philippspiess.com
philippspiess.com
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.
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 :)
I am wondering if they plan to abstract it completely so that everyone uses it without even realizing it.
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:
That said — it only happens when one over-engineers stuff. Make sure to have a single source of truth, and that will be avoided.
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?
Perhaps it would not have been so bad if they just kept the interface the same ...
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.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.
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.
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.
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.
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.
Can you interrupt an arbitrary user-written function that's called inside the rendering stack?
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...
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.
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).
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.
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.
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.
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.
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.
Management is complaining that our website is less responsive than our competition's ...
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.
?? 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.
Also the shape of your global state plays a big role in its maintainability and redux provides good tools and documentation to achieve that.
It sounds a little convoluted, but in practice it's made it easier for developers coming in to discover/predict where things are.
See this issue for details on the docs rewrite:
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.
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).
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:
If you're fairly confident your app won't get too over the top, then by all means go the easy route!
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.
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.
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.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-...
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.
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
• "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.
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...
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.
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.
ahem
https://github.com/reduxjs/react-redux/pull/1000/commits/c9d...
https://github.com/reduxjs/react-redux/pull/1065
https://github.com/reduxjs/react-redux/issues/1177
https://github.com/reduxjs/react-redux/commits/connect-subsc...
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.While LOD would work (e.g., only update on screen names), the work featured to set that up is non trivial
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!).
That rewrite has enabled all of the further functionality that's been implemented in 16.x, including the first stages of "Concurrent Mode".
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.
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.
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
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.
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.
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.
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.
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.
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.
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-...