-
Notifications
You must be signed in to change notification settings - Fork 47k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
React DevTools recording commit without any component re-render #16980
Comments
Made a smaller reproduction. I'm thinking that it has something to do with That has a profile in it as well. Basically the same thing, just a lot less code :) |
Noticed a similar thing past week in a project at work with |
I just noticed this same behavior in the redux Todo app example: https://codesandbox.io/s/github/reduxjs/redux/tree/master/examples/todos |
It just so happens that one of these passes bails out without doing any actual work, but React still calls the DevTools hook and the Profiler isn't "smart" enough to ignore the no-work one. Maybe it should be? |
Thanks for digging into that Brian!
I think that it should, for a few reasons:
I think that it should exclude those kinds of commits. Maybe making it configurable would work, though I'm not sure I can think of a situation where seeing a commit was bailed would be helpful. |
I've noticed it come up in smaller examples in my workshops. Next time I come across it, I'll try to make an even smaller repro. Thanks! |
That would be helpful, thanks. |
I may have misspoke earlier by saying that the "submit" event was the cause of this. Looking again, it looks like the second update is being triggered by another event type (e.g. "blur", "focus", "keydown" - whatever happens to dispatch next after the "submit") Not sure why this is. Nothing seems to be listening for that event type in this project, so it's maybe something React DOM is doing itself? cc @trueadm @necolas since you two have a lot more insight into the event system than I do 😄 |
Looking into this and it seems this might be occurring because ReactDOM's event plugins, that provide functionality to things like Furthermore, these events are all "discrete" events, which means they flush any prior work – likely causing multiple updates here: However, we do have a slightly altered change with the React Flare flag enabled, specifically flushing doesn't occur for multiple discrete events of the same @bvaughn this might fix this issue – please could you test with a build with the React Flare enabled? If you don't use |
I don't think that would apply since it's always the next event (e.g. "blur", "focus", "keydown") rather than the one that scheduled work originally ("submit"). So I think the timestamps wouldn't match.
FWIW the repro project I'm looking at is only using |
@bvaughn > So I think the timestamps wouldn't match. You'd be surprised. Most events all trigger at the same time for interactions, for example, when you click something there's about 10 events that fire – all with the same |
I added a log of the event |
So in this case, after the "submit" event handler, the next time The react/packages/react-reconciler/src/ReactFiberWorkLoop.js Lines 425 to 442 in a1ff9fd
|
Just in case it's helpful. I reproduced this with: https://codesandbox.io/s/input-with-strange-render-13tu0 It's literally just: import React from 'react'
import ReactDOM from 'react-dom'
function App() {
const [name, setName] = React.useState('')
return <input value={name} onChange={e => setName(e.target.value)} />
}
ReactDOM.render(<App />, document.getElementById('root')) |
Ah, thanks! I had been trying to reproduce with an |
Also worth pointing out that this "bug" does not happen when using the root API, only legacy/sync mode. |
I think this bug could be seen as the fault of the synthetic event system (for scheduling the work unnecessarily) or the reconciler (for "committing" the no-op work). I don't think it's the fault of DevTools or the Profiler though, Edit: I've submitted a PR that improves this experience by skipping the empty commits: #17253 |
The following test case demonstrates the current failing behavior in the smaller repro case:
|
DevTools Profiler experience will be improved as of the next extension release. Will follow up with a fix to the underlying React/event issue soonish. |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contribution. |
I think the overall experience here has been improved, but the issue is still pending a decision on #17267. |
This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment! |
Just verified this is still an issue in the current DevTools. |
Yup, #17267 was never reviewed or merged. |
This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment! |
bump |
The issue happens to me, still. |
That's because (as mentioned a couple of comments above yours) the fix in #17267 was never reviewed or landed. |
@bvaughn Are we sure it's caused by the Profiler? Take this code: const App = () => {
const [, setTouched] = useState(false);
const handleBlur = () => {
setTouched(true);
};
console.log('Rendered');
return <input onBlur={handleBlur} />;
}; If you click in and out the input twice, you'll notice 'Rendered' gets printed twice to the console, and not only once as we might expect. Opening the Profiler, you'll see two commits, the first one is normal (and expected), the other one is ghosty as above. Weirdly enough, it's just the second blur that causes the extra print and commit, i.e. if you repeat the operation one or more times, nothing will happen. Doesn't the fact that the App element actually ran (even without causing a re-render) show that the Profiler is not faulty here, and that maybe the event system is? I may be missing something, forgive me if I am! |
@theoavoyne Thanks for sharing this repro. This is a good example of the issue being described in the thread. Indeed, React renders twice– but bails out after the second rendering of Ideally React would not call the DevTools hook at all in this case, which is what my PR #17267 was about. |
Thank you, Brian. Yes I tried that already and noticed exactly what you said. But why is React rendering on the second blur anyway? From my understanding, it shouldn't. And why is it rendering on the second blur but not on subsequent ones? What happens with the subsequent ones (i.e. no rendering at all) is what I expect should happen with the second one. EDIT: this behaviour is observable with events other than blur, so I guess I'm mistaking on what should happen. I just don't get why it renders that second time (even with bailing out). |
Honestly, I forget the reason why React is sometimes able to detect a bailout case earlier (eagerly) and when it sometimes has to do some light work (e.g. rendering a component) before being able to detect bailout. Maybe it has to do with the state/update queue. Regardless, this type of bailout isn't general observable (unless you're specifically looking for it with logs like this) and shouldn't matter much except for cases like this where it causes confusion. I'm going to rebase #17267 and try to get some eyes on it. |
The following test case demonstrates the most recently reported failing behavior:
|
And don't you think that the problem may lie here, e.g. that React renders when it shouldn't? I use I agree that the Profiler should be showing commits only when they happened, so your PR is much needed. But maybe this is pointing to another problem as well? Maybe the event system (since it happens only with event handlers) is messing up at some point? |
Again, I forget the exact scenario when React can't safely detect an eager bailout case without shallowly rendering the tree, but I think this may be related to that. I'm not sure. I'm looking into it. If you'd like to look into it also, I'd be happy to have another set of eyes digging in. I don't think my initial change (#17267) is still relevant after the expiration time to lanes refactor, as we already bail out early if there is no work scheduled on the root: react/packages/react-reconciler/src/ReactFiberWorkLoop.new.js Lines 645 to 653 in e0aa5e2
Need to dig more. |
I think the equivalent fix now would be to not call the DevTools commit hook if both If a subtree renders without generating any effects– should Profiler still care about this? I can see arguments both ways. On the one hand, it's confusing to see an empty tree. On the other, even cheap renders aren't free and these bailouts have cost, so maybe we should report them. I don't think there's any way for DevTools to detect this type of bailout though, since Maybe DevTools could avoid showing an empty commit by "detecting" this case based on whether there are no Fibers passed the |
That's pretty bad I think. It will make the Profiler less predictable.
Totally agree. In the end, I'm not 100% sure that the Profiler is doing anything wrong here. If the extra render caused by the second call to setState wasn't happening, its output would be what's expected and we would never (or maybe it happens in other scenarios? I'm not sure) encounter such an empty tree. BTW, this issue was discussed here. Unfortunately, it wasn't clearly answered. PS: I'm not super familiar with the codebase, so I may not be of great assistance here. Still, I'm digging into it. |
This latest reported issue should be fixed by #22745 |
I'm struggling to make an isolated example of this, but the app where I found this is pretty simple so hopefully it's not too challenging to track down.
So I was profiling https://the-react-bookshelf.netlify.com (locally) and got this when I clicked on the "login" button:
The fact that there was no profile data for a commit is interesting. Each commit should be associated to a state update somewhere in the tree, and wherever that happened should trigger at least one component to re-render, but that didn't appear to happen here.
I also verified that I don't have any components filtered out:
And I didn't filter any commits either:
Here's the exported profile data:
https://gist.github.com/kentcdodds/dbff66043653333cd22cb9261a08550b
And here's the repo where you can pull it down and reproduce yourself: https://github.com/kentcdodds/bookshelf. The component we're looking at is here: https://github.com/kentcdodds/bookshelf/blob/master/src/unauthenticated-app.js
Sorry I can't give a more direct reproduction.
The text was updated successfully, but these errors were encountered: