downshift: Infinite render loop when the toggle button's ref is stored in a state variable
downshiftversion: 8.1.0nodeversion: 18pnpmversion: 8.6.12
Relevant code or config
<div {...getToggleButtonProps({ref: setRef})}>
What you did:
I am trying to set a ref on the toggle button of a custom Select component.
What happened:
Selecting an item on a mobile device triggers a re-render loop.
Uncaught Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
at checkForNestedUpdates (react-dom.development.js:27292:11)
at scheduleUpdateOnFiber (react-dom.development.js:25475:3)
at dispatchSetState (react-dom.development.js:17527:7)
at eval (downshift.esm.js:124:9)
at Array.forEach (<anonymous>)
at eval (downshift.esm.js:122:10)
at safelyDetachRef (react-dom.development.js:22908:22)
at commitMutationEffectsOnFiber (react-dom.development.js:24351:13)
at recursivelyTraverseMutationEffects (react-dom.development.js:24273:7)
at commitMutationEffectsOnFiber (react-dom.development.js:24293:9)
...
Reproduction repository:
Open this codesandbox’s app in a new tab, open the devtool with touch simulation enabled, click the label and select an item.
Problem description:
This only happens when trying to store the toggle button’s ref in a state variable. The issue does not happen when removing the ref prop given to getToggleButtonProps.
This only happens with react-dom’s createRoot function, so it may be related to https://github.com/downshift-js/downshift/issues/1384.
This issue is similar to https://github.com/downshift-js/downshift/issues/1511, but it happens without any other library like formik or floating-ui and the stack trace isn’t the same, so I assumed this is a different underlying issue.
About this issue
- Original URL
- State: closed
- Created 10 months ago
- Reactions: 2
- Comments: 21 (1 by maintainers)
Experiencing this issue as well. Originally, I noticed this as we use Downshift alongside Floating UI which uses this method to store their refs through a callback ref - I’ve replicated that behaviour in CodeSandbox as well.
I believe this comment in the Formik issue is similar to what we’re experiencing, with the stack trace pointing to the
handleRefsfunction.Also similar to the other issue reported with Formik, it seems like the
itemClickfunction is being called multiple times (also logged in the code sandbox above as well).Hi @silviuaavram, problem seems to be on getToggleButtonProps when i pass ref: setReference from floating-ui i have error:
If the ref prop is not passed, everything works fine. If i will pass ref directly to button works everthing fine but i have other error from downshift: downshift: The ref prop “ref” from getToggleButtonProps was not applied correctly on your element.
What does not make sense at all is why only the clicking items triggers this infinite loop. Toggling the button works, arrow keys work, enter key works.
So far I can only tell that, after the clicking action happens, the dispatch kicks a ItemClick state change, state changes, render happens, and then the useControlledReducer will go wild. It will re-calculate state with the ItemClick state change, apparently the new state is different, another render happens, then the re-calculate happens again, and so on.
Hi
I’ve run into the same kind of the issue today, If you look closer into implementation it does make sense that re-render occurs, following props getters:
Are calling
refcallback (in this case, setState) when we callgetXPropsgetter. If you’re usingsetStatefor storing ref you’re running into setState/re-render loop, pseudo code:One thing is weird to me though, why this is only reproducible on
touchdevices. That’s why my theory might be completely invalidated by downshift team.My fix for this issue is wrapping call
getXPropsinto useMemo with proper dependencies. In your case @aliceHendicott my fix is: