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: react correctly to external controlled updates #56678

Merged
merged 5 commits into from
Dec 1, 2023

Conversation

ciampo
Copy link
Contributor

@ciampo ciampo commented Nov 30, 2023

Fixes #56667
Fixes the issue described in #56500

What?

This PR improves the internal logic in ToggleGroupControl used to detect whether the component is used in controlled or uncontrolled more

Why?

The previous logic wasn't reacting correctly to new values being passed to the component, and as a result it wasn't causing the component to update with the new values.

How?

Using useState instead of useRef to track internal state causes the component to re-render more eagerly when the "controlled" flag flips.

Testing Instructions

@ciampo ciampo self-assigned this Nov 30, 2023
@ciampo ciampo requested review from jsnajdr, tellthemachines and a team November 30, 2023 11:48
@ciampo ciampo marked this pull request as ready for review November 30, 2023 11:48
@ciampo ciampo added [Type] Bug An existing feature does not function as intended [Package] Components /packages/components labels Nov 30, 2023
@ciampo ciampo changed the title ToggleGroupControl: react better to external controlled updates ToggleGroupControl: react correctly to external controlled updates Nov 30, 2023
Copy link

github-actions bot commented Nov 30, 2023

Flaky tests detected in 749a884.
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/7052472587
📝 Reported issues:

Copy link
Member

@jsnajdr jsnajdr left a comment

Choose a reason for hiding this comment

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

Yes, this fixes the bug. The problem was that the render where valueProp changed from foo to bar was still returning value: undefined, because the hasEver value is still false. It's updated only a bit later in the effect.

This PR immediately schedules a next render where hasEver will be true and it will return value: 'bar'.

An implementation that doesn't need the extra render but returns the right value immediately when valueProp changes:

function useComputedControlled( value ) {
  const prevValue = usePrevious( value );
  const prevControlled = useRef( false );
  const isControlled = prevControlled.current || ( prevValue.current !== undefined && value !== undefined && prevValue.current !== value );
  useEffect( () => {
    prevControlled.current = isControlled;
  }, [ isControlled ] );
    
  if ( isControlled ) {
    return { value: value ?? '', defaultValue: undefined };
  }

  return { value: undefined, defaultValue: value };
}

valueProp !== previousValueProp;
previousValueProp !== undefined &&
valueProp !== previousValueProp
);
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 change this to:

if ( ! hasEver && definedAndDifferent( prevVal, val ) {
  setHasEver( true );
}

That prevents scheduling setState calls that will be noops anyway, changing false to false.

Also, once in controlled mode, the component can't change back to uncontrolled, right? The component always starts as uncontrolled, and switches to controlled on the first re-render that has a different valueProp.

Then a better name for hasEver... is simply inControlledMode.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That prevents scheduling setState calls that will be noops anyway, changing false to false.

That's a good suggestion, thank you!

Also, once in controlled mode, the component can't change back to uncontrolled, right?

Yes, this custom behaviour was necessary to be backwards compatible with how the component used to work before supporting uncontrolled mode.

I'm not going to implement this suggestion since I decided to refactor the hook following your other suggestion

@ciampo
Copy link
Contributor Author

ciampo commented Nov 30, 2023

An implementation that doesn't need the extra render but returns the right value immediately when valueProp changes:

I like this! I went ahead and refactored the hook following your suggestion, plus with some minor adjustments (for example, prevValue doesn't have a current property).

I'll wait for folks to have a final round of review before merging

@ciampo ciampo force-pushed the fix/toggle-group-control-controlled-external-updates branch from 2924e4d to 749a884 Compare November 30, 2023 21:27
Copy link
Contributor

@tellthemachines tellthemachines left a comment

Choose a reason for hiding this comment

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

This fixes the issue and doesn't seem to break other instances of ToggleGroupControl. Changes LGTM and thanks for adding the test!

Copy link
Member

@jsnajdr jsnajdr left a comment

Choose a reason for hiding this comment

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

Thanks for accepting my suggestions 😉 Now I approve the PR even more.

@ciampo ciampo merged commit bd62f61 into trunk Dec 1, 2023
52 checks passed
@ciampo ciampo deleted the fix/toggle-group-control-controlled-external-updates branch December 1, 2023 09:43
@github-actions github-actions bot added this to the Gutenberg 17.3 milestone Dec 1, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Package] Components /packages/components [Type] Bug An existing feature does not function as intended
Projects
Status: Done 🎉
Development

Successfully merging this pull request may close these issues.

ToggleGroupControlAsRadioGroup value is always undefined
3 participants