Skip to content
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

ToggleGroupControl: Don't set value on focus after a reset #66151

Merged
merged 4 commits into from
Oct 16, 2024

Conversation

mirka
Copy link
Member

@mirka mirka commented Oct 16, 2024

Bug discovered in #65386 (comment)

What?

Fixes an incomplete bug fix in #65892 where ToggleGroupControl could still autoselect an option on focus after the selected value was reset by the consumer.

Testing Instructions for Keyboard

Modify the Storybook story so it has a reset button, like this:

diff --git a/packages/components/src/toggle-group-control/stories/index.story.tsx b/packages/components/src/toggle-group-control/stories/index.story.tsx
index 92f1e60762..01e5fc4eae 100644
--- a/packages/components/src/toggle-group-control/stories/index.story.tsx
+++ b/packages/components/src/toggle-group-control/stories/index.story.tsx
@@ -48,15 +48,18 @@ const Template: StoryFn< typeof ToggleGroupControl > = ( {
 		useState< ToggleGroupControlProps[ 'value' ] >();
 
 	return (
-		<ToggleGroupControl
-			__nextHasNoMarginBottom
-			{ ...props }
-			onChange={ ( ...changeArgs ) => {
-				setValue( ...changeArgs );
-				onChange?.( ...changeArgs );
-			} }
-			value={ value }
-		/>
+		<>
+			<ToggleGroupControl
+				__nextHasNoMarginBottom
+				{ ...props }
+				onChange={ ( ...changeArgs ) => {
+					setValue( ...changeArgs );
+					onChange?.( ...changeArgs );
+				} }
+				value={ value }
+			/>
+			<button onClick={ () => setValue( undefined ) }>Reset</button>
+		</>
 	);
 };

1. Select a value.
2. Reset the value using the reset button.
3. Using the keyboard, focus back onto the component. A value should not be selected automatically.

@mirka mirka added [Type] Bug An existing feature does not function as intended [Package] Components /packages/components Backport to WP 6.7 Beta/RC Pull request that needs to be backported to the WordPress major release that's currently in beta labels Oct 16, 2024
@mirka mirka self-assigned this Oct 16, 2024
@mirka mirka requested a review from ajitbohra as a code owner October 16, 2024 00:03
Copy link

github-actions bot commented Oct 16, 2024

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 props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: mirka <0mirka00@git.wordpress.org>
Co-authored-by: tyxla <tyxla@git.wordpress.org>
Co-authored-by: ciampo <mciampini@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

Comment on lines +145 to +146
toggleGroupControlContext.value === null ||
toggleGroupControlContext.value === '';
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Due to how we add a layer of value conversion in controlled mode, the Ariakit store will hold a null value on first render with no value provided, but then an empty string after a consumer resets the value to undefined.

Comment on lines +76 to +81
// Ensures that the active id is also reset after the value is "reset" by the consumer.
useEffect( () => {
if ( selectedValue === '' ) {
radio.setActiveId( undefined );
}
}, [ radio, selectedValue ] );
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can feel the tech debt catching up to us 😅

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Food for thought: I wonder if this can be improved with a centralized controlled component layer that all controlled components would use. Probably a too idealistic idea, since some components use Ariakit and others do not, and this layer would be quite complicated to cover all cases.

Copy link
Contributor

@ciampo ciampo Oct 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that the most practical fix here is to change the APIs to follow the standard convention for controlled / uncontrolled component. Since that would be a breaking change, we could just release a V2 of the component that would give us also a chance to:

  • implement / test the new design spec alongside the legacy one
  • review the current API strategy around "deselectability" and potentially multiple selection (should they be separate components? Or different subcomponents?)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this case specifically, I think there might be room to argue that resetting the active id on empty-ish (or invalid) values should be handled by Ariakit out of the box. I'll try and follow up.

Copy link

Flaky tests detected in 1c8b689.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/11356382384
📝 Reported issues:

Copy link
Member

@tyxla tyxla left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks for the fix 👍 🚀

@@ -141,10 +141,14 @@ function ToggleGroupControlOptionBase(
<Ariakit.Radio
disabled={ disabled }
onFocusVisible={ () => {
const selectedValueIsNullish =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming nit: nullish often refers to null or undefined, so if someone isn't reading closely, they might be confused and expect this checks for undefined and not an empty string.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe selectedValueIsEmpty ? Or even inlining the check?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be fine with inlining it 👍

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I'll go with selectedValueIsEmpty here. (It started as an inline condition, but it was hard to parse/understand so I prefer it named.)

Comment on lines +76 to +81
// Ensures that the active id is also reset after the value is "reset" by the consumer.
useEffect( () => {
if ( selectedValue === '' ) {
radio.setActiveId( undefined );
}
}, [ radio, selectedValue ] );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Food for thought: I wonder if this can be improved with a centralized controlled component layer that all controlled components would use. Probably a too idealistic idea, since some components use Ariakit and others do not, and this layer would be quite complicated to cover all cases.

@@ -175,6 +175,32 @@ describe.each( [
expect( radio ).not.toBeChecked();
} );

if ( mode === 'controlled' ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we move it next to the other controlled-only tests?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No strong opinion, but I think I prefer it here because it's closely related to the test right above it.

@mirka mirka enabled auto-merge (squash) October 16, 2024 15:55
@mirka mirka merged commit b9b6330 into trunk Oct 16, 2024
65 of 66 checks passed
@mirka mirka deleted the fix/toggle-group-control-focus branch October 16, 2024 16:26
@github-actions github-actions bot added this to the Gutenberg 19.5 milestone Oct 16, 2024
Copy link

There was a conflict while trying to cherry-pick the commit to the wp/6.7 branch. Please resolve the conflict manually and create a PR to the wp/6.7 branch.

PRs to wp/6.7 are similar to PRs to trunk, but you should base your PR on the wp/6.7 branch instead of trunk.

# Checkout the wp/6.7 branch instead of trunk.
git checkout wp/6.7
# Create a new branch for your PR.
git checkout -b my-branch
# Cherry-pick the commit.
git cherry-pick b9b633059bb448fa4281c43d8fa8d696ad6881cd
# Check which files have conflicts.
git status
# Resolve the conflict...
# Add the resolved files to the staging area.
git status
git add .
git cherry-pick --continue
# Push the branch to the repository
git push origin my-branch
# Create a PR and set the base to the wp/6.7 branch.
# See https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/changing-the-base-branch-of-a-pull-request.

@ciampo
Copy link
Contributor

ciampo commented Oct 16, 2024

@mirka just a ping in case you didn't notice the failed backport

mirka added a commit that referenced this pull request Oct 17, 2024
* Add test

* ToggleGroupControl: Don't set value on focus after a reset

* Add changelog

* Rename to `selectedValueIsEmpty`

Co-authored-by: mirka <0mirka00@git.wordpress.org>
Co-authored-by: tyxla <tyxla@git.wordpress.org>
Co-authored-by: ciampo <mciampini@git.wordpress.org>
# Conflicts:
#	packages/components/CHANGELOG.md
@mirka mirka added Backported to WP Core Pull request that has been successfully merged into WP Core and removed Backport to WP 6.7 Beta/RC Pull request that needs to be backported to the WordPress major release that's currently in beta labels Oct 17, 2024
@mirka
Copy link
Member Author

mirka commented Oct 17, 2024

Backported at 4a7de72

karthick-murugan pushed a commit to karthick-murugan/gutenberg that referenced this pull request Nov 13, 2024
…#66151)

* Add test

* ToggleGroupControl: Don't set value on focus after a reset

* Add changelog

* Rename to `selectedValueIsEmpty`

Co-authored-by: mirka <0mirka00@git.wordpress.org>
Co-authored-by: tyxla <tyxla@git.wordpress.org>
Co-authored-by: ciampo <mciampini@git.wordpress.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Backported to WP Core Pull request that has been successfully merged into WP Core [Package] Components /packages/components [Type] Bug An existing feature does not function as intended
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants