-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Fix newlines in block editor after a user mention #41749
Conversation
Size Change: +10 B (0%) Total Size: 1.24 MB
ℹ️ View Unchanged
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In testing, I'm still seeing some occasional glitches 😢 Not only the newline issues, but also the suggestions popover not appearing when it should.
From what I could tell, these changes from #41382 are also problematic. If you console.log
in that useEffect
, you can tell that the frequency of it firing has greatly increased (while typing in the Paragraph block). And the number of uncaught promise errors have gone up as well. (A small number of those were pre-existing, so there's probably also a pre-existing race bug somewhere.)
Do you want to try reworking that useEffect
so we can keep the previous behavior (i.e. only fire on textContent
changes) at least? We could also revert #41382 in the meantime, if it isn't a quick fix.
const onChangeOptions = useCallback( | ||
( options ) => { | ||
setSelectedIndex( | ||
options.length === filteredOptions.length ? selectedIndex : 0 | ||
); | ||
setFilteredOptions( options ); | ||
announce( options ); | ||
}, | ||
[ announce, filteredOptions.length, selectedIndex ] | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So... I'm starting to think about when it's better to use a useCallback
vs. putting the callback in a ref
.
In this case, the ref
approach would be to change this
gutenberg/packages/components/src/autocomplete/autocompleter-ui.js
Lines 40 to 42 in 982cfc7
useLayoutEffect( () => { | |
onChangeOptions( items ); | |
}, [ onChangeOptions, items ] ); |
to something like this:
const onChangeOptionsRef = useRef( onChangeOptions );
onChangeOptionsRef.current = onChangeOptions;
useLayoutEffect( () => {
onChangeOptionsRef.current( items );
}, [ items ] );
The reason I think this approach is preferable here is because, it should be AutocompleterUI
's responsibility that its own effects work correctly. If we instead address it with a useCallback
in the consumer, then all potential consumers will need to do the same thing, which is hard to know about.
I might be missing something though. What do you think?
I'm going to review this later today, but here's also an alternative plan, in case we want to fix ASAP the regression explained in this PR (and in #41770):
This approach could take some pressure away from landing these 2 bug fixes quickly. What do folks think? |
Closing, as the regression this was meant to address has been reverted: #41820 |
What?
Fix #41724
Memoize
onChangeOptions
and subsequent dependencies.Why?
When
onChangeOptions
was added as auseLayoutEffect
dependency in #41382, new lines after user mentions became very unreliable.I think (not 100% sure, but it bears out in my testing) that a race condition was introduced. When the new dep was placed, the effect began firing on every render because
onChangeOptions
was actually being redeclared each time. This effect firing would in turn call two state setters (setSelectedIndex
andsetFilteredOptions
). If either of these was updated with a new value, a new render would trigger, repeating the cycle. It wasn't quite an infinite loop, so no errors appeared, but it did create frequent cases where theuseEffect
responsible for callingreset()
would be prevented from firing.My working theory is that because
useLayoutEffect
fires before the render completes, the state was changing before thatreset
function could do its job and clear the stored autocomplete input... so whenEnter/Return
was pressed, the component inserted the value it had previously been asked to insert from the completion popover.How?
Since the problem appeared to stem from firing that
useLayoutEffect
too often, we can wraponChangeOptions
in auseCallback
. The function itself shouldn't change, so effectively I expect this effect to only fire on changes toitems
.This, in turn, required
announce
to get a similaruseCallback
wrapper.onChangeOptions
should now only actually fire when there's a change to either the length of thefilterOptions
array, or theselectedIndex
(which should be exactly what we want).Testing Instructions
@
followed by part of another user's name. Select them withReturn/Enter
space
and enter a few characters of textReturn/Enter
At least some (probably most if not all) of the time, your followup text should be erased by the autocompleter.
After applying this PR, repeat the same steps. You should get a new paragraph block when hitting
Return/Enter
each time.