flume: Warning: Maximum update depth exceeded on basic example
Hey man, great lib in general, some cool features here!
I’m trying to get the basics right and I’m having trouble with the editor behaving in this unusual way. As soon as I connect any two nodes even with the basic example here: https://flume.dev/docs/basic-config, I start getting this error non stop:
react_devtools_backend.js:4026 Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn’t have a dependency array, or one of the dependencies changes on every render. at NodeEditor (http://localhost:3000/static/js/bundle.js:10660:30) at div at App
I’ve compiled it by running the latest create react app, this is how my package.json looks like:
"packages": {
"": {
"name": "cpq-actionprocess",
"version": "0.1.0",
"dependencies": {
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.2.0",
"@testing-library/user-event": "^13.5.0",
"flume": "^0.8.0",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
}
},
Hope this is useful.
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Reactions: 2
- Comments: 18 (4 by maintainers)
I believe I found the high-level cause and root cause of the issue.
High-level cause
The generic cause is this: Flume doesn’t support out-of-the-box React 18’s new concurrent mode (and it doesn’t advertise it does, as it has React 17 in its peer dependencies, not React 18). Using the minimal example from the previous post, I could alternate between the sample crashing or not depending wether I used
ReactDom.render()(legacy mode even in React 18, does not trigger the crash) andReactDom.createRoot().render()(concurrent mode, triggers the crash).As such, there is a first workaround for users currently experiencing this issue: if at all possible, switch back to using legacy root creation, which will disable concurrent mode and prevent the issue from happening. You can find the API doc here: https://reactjs.org/docs/react-dom.html#render If/when the root cause is fixed in Flume, you’ll be able to go back to the current “concurrent” mode.
Root cause
The issue is caused in the NodeEditor component, having a LayoutEffect hook depending on a state variable (
shouldRecalculateConnections), and then setting a new value for that state, combined with having a “manual” trigger function also being called in an effect deeper in the render tree (IoPorts.js/Input). I can’t wrap my head around a precise, step-by-step trace of exactly what happens, but it seems to me like having an effect depending on a state it also sets, and having that same state set to something else in another render, provokes this.You can find the concerned code here: https://github.com/chrisjpatty/flume/blob/0372e92e65065772234ab83c2a106da43279915a/src/index.js#L97:L106
However, since we know the state dependency is what causes the infinite recursion, we can remove or replace that state to prevent the issue from happening.
Fix
Note: I don’t believe any of the two suggestions below would break on React < 18, which is important while the official peer dependency is React 17.
Depending on the intended behavior (which I couldn’t conclude on my own based on the code), I see two options:
Option 1: completely remove the
shouldRecalculateConnectionsstateThis is the option I believe is less likely to be the intended behavior.
One option is to remove the
shouldRecalculateConnectionsstate, and simply callrecalculateConnections()every time we want, instead of depending on an effect to do that calculation.The resulting code is as follows:
When tracing calls locally,
triggerRecalculationworks like before, since it previously setting the state resulted in a recalculation. However, the layout effect now callsrecalculateConnectionsmore often, since it doesn’t have a quick break when the state var would previously bypass the calculation. It seems to be called every time a node changes, so not that often, but still.Option 2: replace the state with an “initial did X” ref
This is the option I believe closest matches the current behavior.
The other option is based on the fact that only
triggerRecalculationever setsshouldRecalculateConnectionsto true, thus causing a recalculation in the effect hook. I also see that the initial value ofshouldRecalculateConnectionsis true, which means that on initial render, the recalculation will also be called.If the intended behavior is “recalculate on first render, then on every trigger”, then a ref var can be used instead of state to accomplish this, as follows:
I tested both options, and both prevented the crash from happening.
Of course, my knowledge of the code base and its intricacies is limited, and I could fail to see an issue with the suggestions above, but from my perspective, it seems to work as the original.
Workarounds
Any one of these can fix the issue:
Switch back to non-concurrent React
As I mentioned above, one workaround is to opt-out of React’s current “concurrent” mode, by switching to
ReactDom.render()instead ofReactDom.createRoot().render().Use
patch-packageFor users of
patch-package, I also wrote the following patch, which applies “Option 2”, if you believe it is sufficient for your needs:The patch fixed the issue locally.
Final notes
If the library’s owner accepts pull requests, I will be happy to open one with the fix in option 2 (or something else if you have comments). As the fix is quite simple, you might also prefer to make the changes yourself.
And, thanks, by the way, for this awesome library 🙂
@pascallapradebrite4
As a consumer of this library I would appreciate a PR with that fix a great deal! 😄
@chrisjpatty wonderful it’s working like a charm now ! Cheers @pascallapradebrite4 ! gotta love it when things run smoothly !! Bravo lads
This should be fixed now in version 0.8.1. Thanks for everyone’s patience on this, and thanks especially @pascallapradebrite4 for the great writeup, was very helpful!
@osafafi I’m looking into it right now. Having some trouble reproducing but I should have a patch soon hopefully.
It looks like the recursion takes place in
src/components/IoPorts.js. I’ve logged all the effects in this file and it looks like the recursion takes place inside the useEffect on lines 119-123.The variable
isConnectedis false andprevConnectedis true when the function is called based on dependencies. I don’t know what this code does, but removing it doesn’t seem to cause any collateral damage, at least in the tests I’ve done.I’ll do more tests tomorrow, but @chrisjpatty , can you help me figuring out what this code snipped does?
Getting this error with the simplest possible example from the Getting Started guide in the documentation.
Create two nodes, connect them via one wire, and the console will spit this error out multiple times per second.
Using Yarn, create-react-app, and React v18 and Flume v0.8
Thanks @bfig, do you have a repo I could look at to try to recreate this?