-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
SlotFill: fix a bug with storing stale fillProps #67000
Conversation
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.
To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
Flaky tests detected in 4c78204. 🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/11854163018
|
@@ -21,8 +21,10 @@ export default function useSlot( name: SlotKey ) { | |||
|
|||
const api = useMemo( | |||
() => ( { | |||
updateSlot: ( fillProps: FillProps ) => | |||
registry.updateSlot( name, fillProps ), | |||
updateSlot: ( |
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.
Where is this one used. or is it not used at all? I see the function arguments modified but I don't see where the calling of the function is updated.
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.
The updateSlot
method is used in the Slot
component. Called on every render, in a effect without dependencies, to make sure that the latest fillProps
is always synced promptly to the registry.
The bug being fixed is triggered when, for any reason, the Slot
is rerendered one more time before unmounting. Then updateSlot
would store a stale fillProps
value at a time when a new Slot
is already being mounted. The unregisterSlot
method already has exactly the same ref
check.
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.
But this one updateSlot that is returned from useSlot, is not used right?
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.
Oh yes, this is a good observation. The updateSlot
and unregisterSlot
functions returned from useSlot
are not really used. And I think I could get rid of the entire api
object and return only the slot
. I'll do that in a followup PR.
const fillPropsRef = useRef( fillProps ); | ||
useLayoutEffect( () => { | ||
fillPropsRef.current = fillProps; | ||
}, [ fillProps ] ); |
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 general this is an indication that something should have been defined using the new useEvent
maybe.
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.
Unfortunately useEvent
is specialized to support only callbacks. Here we have a value that is a regular object.
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.
What I was thinking is that we could do something like
const registerSlotEffect = useEvent(() => {
registerSlot( name, ref, fillProps );
} );
useEffect(() => {
registerSlotEffect();
}, [ name ] );
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.
Yes, if we really wanted to use useEvent
, it's possible, but it doesn't really improve the code IMO. Also the fillPropsRef.current
value is used in two effect hooks, which makes useEvent
even less attractive.
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.
Left some random comments but to be honest, this one is hard to review. I think I'd just to trust the tests and your confidence in this PR :)
01c1142
to
4c78204
Compare
OK, I updated the PR to be smaller and more focused. I'll do the bigger refactors separately. |
} | ||
|
||
slots.delete( name ); |
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.
Here I'm just tidying up the code, no behavior change. unregisterSlot
and updateSlot
now do the same ref
code and the code looks also the same.
if ( ! fillsForName ) { | ||
return; | ||
} | ||
const unregisterFill: SlotFillBubblesVirtuallyContext[ 'unregisterFill' ] = |
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.
Here I'm fixing a typo in the type: changing registerFill
to unregisterFill
. Both functions have the same signature, so the bug didn't have any observable consequences, but worth fixing anyway.
useLayoutEffect( () => { | ||
registry.updateSlot( name, fillProps ); | ||
registry.updateSlot( name, ref, fillPropsRef.current ); |
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.
This is a critical line in the patch, adding a ref
param to the updateSlot
call.
fillProps | ||
) => { | ||
const slot = slots.get( name ); | ||
if ( ! slot ) { | ||
return; | ||
} | ||
|
||
if ( slot.ref !== ref ) { | ||
return; | ||
} |
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.
This is second critical line of the patch, adding a check if ref
matches.
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.
Thank you, @jsnajdr!
P.S. Inline comments were very helpful!
Fixes a
SlotFill
bug where a slot is sometimes rendered with old stalefillProps
. It's described and discussed here: #65907 (comment). In short, theupdateSlot
method needs to check aref
value to determine if it's still the current registered slot. When an old slot is unregistered and new slot is registered, there is a short period of time where both are semi-registered and special care is needed.That's the first commit in this PR. In addition, I'm doing a series of refactorings ofSlotFill
in order to put it in a better shape: