-
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 modal dismissers in development mode #64132
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. |
Size Change: +12 B (0%) Total Size: 1.76 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.
Thanks, @jsnajdr!
This test went well for me, and switching to Set
makes more sense here, especially when the effect is run in StrictMode.
Let's include @stokesman in props when this is merged.
@@ -89,8 +89,7 @@ function useEditorCommandLoader() { | |||
name: 'core/open-shortcut-help', | |||
label: __( 'Keyboard shortcuts' ), | |||
icon: keyboard, | |||
callback: ( { close } ) => { | |||
close(); |
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.
We should probably restore this. Ref: #64075 (comment)
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.
Restored after we verified that the modal dismissal work even when the close()
call is missing 👍
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.
With regards to automated testing, Modal
has some unit tests for dismissal of siblings but they weren’t catching this issue with the effect. Maybe there’s a way to do it with the unit tests. If not, perhaps there could be value in omitting the explicit close
call and adding an e2e test that would fail in case of a regression. Though, I'm not even sure if that could work, do the e2e test run with StrictMode active? Just a thought, likely one of you have better ones 🙂
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.
E2E tests run in production mode. Unit tests might be more suitable for this.
c39fa9d
to
d13130d
Compare
@@ -350,7 +351,7 @@ function UnforwardedModal( | |||
); | |||
|
|||
return createPortal( | |||
<ModalContext.Provider value={ nestedDismissers.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.
I suppose avoiding this reading of the ref during render was the idea behind using state instead. Or was it something else?
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 state is also often used for one-time initializations. See https://tkdodo.eu/blog/use-state-for-one-time-initializations.
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 main advantage of useState
for one-time initialization like this is that it supports an initialization function. You can write:
useState( () => new Set() );
and avoid creating a new set on each render. It's created only when useState
really wants to init and calls the initializer. On the other hand, with useRef
you can only do this:
useRef( new Set() );
In this particular case it doesn't matter much, but for more expensive initializations it can make a difference.
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.
Ah, makes sense. Indeed it would hardly matter here and probably neither would there be a consequence to reading the ref in this case. All around it’s better though. Thanks!
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 useState
initialization pattern has been becoming popular recently because of React 19 and the React Compiler: I don't remember the exact details right now, but the compiler issues warnings about some useRef
usages, and switching to useState
makes it happy.
Flaky tests detected in d13130d. 🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/10179732247
|
Followup to #64019 that addresses the root cause.
Apparently @stokesman was working on the same task in parallel, creating #64075 🙂
Both fixes have one part in common: a modal never calls its own dismisser.
My PR additionally converts the array to a set. I think it makes the logic more reliable, as it makes the modal effect cleanup always remove only its own dismisser. When calling
dismissers.shift()
, I'm not so sure what I'm actually removing from the start of the array.