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

InputControl: Fix acceptance of falsy values in controlled updates #42484

Merged
merged 11 commits into from
Jul 29, 2022

Conversation

stokesman
Copy link
Contributor

@stokesman stokesman commented Jul 17, 2022

What?

Makes InputControl handling of updates from props distinct from any other action.

Why?

To fix #42455. Presently the RESET action is used to handle updates from props and that favors a fallback value over a falsy one:

case actions.RESET:
nextState.error = null;
nextState.isDirty = false;
nextState.value = action.payload.value || state.initialValue;

That means that that falsy values coming from props will not be used which is the cause of the issue.

How?

  • In order to have a test that will fail in case of another regression, an existing test is updated to use a falsy value
  • The handling of updates from props made distinct to any other existing action types

Testing Instructions

  1. After fetching and switching to this branch, checkout the first commit
  2. run npm test-unit input-control to verify the changed test fails
  3. Switch back to the whole branch git switch -
  4. repeat step 2 but note the test passes
  5. Try reproducing the issue in Inspector Controls: Inputs aren't clearing after control is reset via panel menu  #42455 as detailed in its reproduction instructions

@stokesman stokesman added the [Package] Components /packages/components label Jul 17, 2022
@github-actions
Copy link

github-actions bot commented Jul 17, 2022

Size Change: +48 B (0%)

Total Size: 1.26 MB

Filename Size Change
build/components/index.min.js 230 kB +48 B (0%)
ℹ️ View Unchanged
Filename Size
build/a11y/index.min.js 982 B
build/annotations/index.min.js 2.76 kB
build/api-fetch/index.min.js 2.26 kB
build/autop/index.min.js 2.14 kB
build/blob/index.min.js 475 B
build/block-directory/index.min.js 6.58 kB
build/block-directory/style-rtl.css 990 B
build/block-directory/style.css 991 B
build/block-editor/default-editor-styles-rtl.css 378 B
build/block-editor/default-editor-styles.css 378 B
build/block-editor/index.min.js 153 kB
build/block-editor/style-rtl.css 14.6 kB
build/block-editor/style.css 14.6 kB
build/block-library/blocks/archives/editor-rtl.css 61 B
build/block-library/blocks/archives/editor.css 60 B
build/block-library/blocks/archives/style-rtl.css 65 B
build/block-library/blocks/archives/style.css 65 B
build/block-library/blocks/audio/editor-rtl.css 150 B
build/block-library/blocks/audio/editor.css 150 B
build/block-library/blocks/audio/style-rtl.css 103 B
build/block-library/blocks/audio/style.css 103 B
build/block-library/blocks/audio/theme-rtl.css 110 B
build/block-library/blocks/audio/theme.css 110 B
build/block-library/blocks/avatar/editor-rtl.css 116 B
build/block-library/blocks/avatar/editor.css 116 B
build/block-library/blocks/avatar/style-rtl.css 59 B
build/block-library/blocks/avatar/style.css 59 B
build/block-library/blocks/block/editor-rtl.css 161 B
build/block-library/blocks/block/editor.css 161 B
build/block-library/blocks/button/editor-rtl.css 441 B
build/block-library/blocks/button/editor.css 441 B
build/block-library/blocks/button/style-rtl.css 542 B
build/block-library/blocks/button/style.css 542 B
build/block-library/blocks/buttons/editor-rtl.css 292 B
build/block-library/blocks/buttons/editor.css 292 B
build/block-library/blocks/buttons/style-rtl.css 275 B
build/block-library/blocks/buttons/style.css 275 B
build/block-library/blocks/calendar/style-rtl.css 207 B
build/block-library/blocks/calendar/style.css 207 B
build/block-library/blocks/categories/editor-rtl.css 84 B
build/block-library/blocks/categories/editor.css 83 B
build/block-library/blocks/categories/style-rtl.css 79 B
build/block-library/blocks/categories/style.css 79 B
build/block-library/blocks/code/style-rtl.css 103 B
build/block-library/blocks/code/style.css 103 B
build/block-library/blocks/code/theme-rtl.css 124 B
build/block-library/blocks/code/theme.css 124 B
build/block-library/blocks/columns/editor-rtl.css 108 B
build/block-library/blocks/columns/editor.css 108 B
build/block-library/blocks/columns/style-rtl.css 406 B
build/block-library/blocks/columns/style.css 406 B
build/block-library/blocks/comment-author-avatar/editor-rtl.css 125 B
build/block-library/blocks/comment-author-avatar/editor.css 125 B
build/block-library/blocks/comment-content/style-rtl.css 92 B
build/block-library/blocks/comment-content/style.css 92 B
build/block-library/blocks/comment-template/style-rtl.css 187 B
build/block-library/blocks/comment-template/style.css 185 B
build/block-library/blocks/comments-pagination-numbers/editor-rtl.css 123 B
build/block-library/blocks/comments-pagination-numbers/editor.css 121 B
build/block-library/blocks/comments-pagination/editor-rtl.css 222 B
build/block-library/blocks/comments-pagination/editor.css 209 B
build/block-library/blocks/comments-pagination/style-rtl.css 235 B
build/block-library/blocks/comments-pagination/style.css 231 B
build/block-library/blocks/comments-title/editor-rtl.css 75 B
build/block-library/blocks/comments-title/editor.css 75 B
build/block-library/blocks/comments/editor-rtl.css 834 B
build/block-library/blocks/comments/editor.css 832 B
build/block-library/blocks/comments/style-rtl.css 632 B
build/block-library/blocks/comments/style.css 630 B
build/block-library/blocks/cover/editor-rtl.css 615 B
build/block-library/blocks/cover/editor.css 616 B
build/block-library/blocks/cover/style-rtl.css 1.55 kB
build/block-library/blocks/cover/style.css 1.55 kB
build/block-library/blocks/embed/editor-rtl.css 293 B
build/block-library/blocks/embed/editor.css 293 B
build/block-library/blocks/embed/style-rtl.css 410 B
build/block-library/blocks/embed/style.css 410 B
build/block-library/blocks/embed/theme-rtl.css 110 B
build/block-library/blocks/embed/theme.css 110 B
build/block-library/blocks/file/editor-rtl.css 300 B
build/block-library/blocks/file/editor.css 300 B
build/block-library/blocks/file/style-rtl.css 253 B
build/block-library/blocks/file/style.css 254 B
build/block-library/blocks/file/view.min.js 346 B
build/block-library/blocks/freeform/editor-rtl.css 2.44 kB
build/block-library/blocks/freeform/editor.css 2.44 kB
build/block-library/blocks/gallery/editor-rtl.css 948 B
build/block-library/blocks/gallery/editor.css 950 B
build/block-library/blocks/gallery/style-rtl.css 1.53 kB
build/block-library/blocks/gallery/style.css 1.53 kB
build/block-library/blocks/gallery/theme-rtl.css 108 B
build/block-library/blocks/gallery/theme.css 108 B
build/block-library/blocks/group/editor-rtl.css 333 B
build/block-library/blocks/group/editor.css 333 B
build/block-library/blocks/group/style-rtl.css 57 B
build/block-library/blocks/group/style.css 57 B
build/block-library/blocks/group/theme-rtl.css 78 B
build/block-library/blocks/group/theme.css 78 B
build/block-library/blocks/heading/style-rtl.css 76 B
build/block-library/blocks/heading/style.css 76 B
build/block-library/blocks/html/editor-rtl.css 327 B
build/block-library/blocks/html/editor.css 329 B
build/block-library/blocks/image/editor-rtl.css 736 B
build/block-library/blocks/image/editor.css 737 B
build/block-library/blocks/image/style-rtl.css 627 B
build/block-library/blocks/image/style.css 630 B
build/block-library/blocks/image/theme-rtl.css 110 B
build/block-library/blocks/image/theme.css 110 B
build/block-library/blocks/latest-comments/style-rtl.css 284 B
build/block-library/blocks/latest-comments/style.css 284 B
build/block-library/blocks/latest-posts/editor-rtl.css 199 B
build/block-library/blocks/latest-posts/editor.css 198 B
build/block-library/blocks/latest-posts/style-rtl.css 463 B
build/block-library/blocks/latest-posts/style.css 462 B
build/block-library/blocks/list/style-rtl.css 88 B
build/block-library/blocks/list/style.css 88 B
build/block-library/blocks/media-text/editor-rtl.css 266 B
build/block-library/blocks/media-text/editor.css 263 B
build/block-library/blocks/media-text/style-rtl.css 493 B
build/block-library/blocks/media-text/style.css 490 B
build/block-library/blocks/more/editor-rtl.css 431 B
build/block-library/blocks/more/editor.css 431 B
build/block-library/blocks/navigation-link/editor-rtl.css 705 B
build/block-library/blocks/navigation-link/editor.css 703 B
build/block-library/blocks/navigation-link/style-rtl.css 115 B
build/block-library/blocks/navigation-link/style.css 115 B
build/block-library/blocks/navigation-submenu/editor-rtl.css 296 B
build/block-library/blocks/navigation-submenu/editor.css 295 B
build/block-library/blocks/navigation-submenu/view.min.js 423 B
build/block-library/blocks/navigation/editor-rtl.css 2.03 kB
build/block-library/blocks/navigation/editor.css 2.04 kB
build/block-library/blocks/navigation/style-rtl.css 1.96 kB
build/block-library/blocks/navigation/style.css 1.95 kB
build/block-library/blocks/navigation/view-modal.min.js 2.78 kB
build/block-library/blocks/navigation/view.min.js 443 B
build/block-library/blocks/nextpage/editor-rtl.css 395 B
build/block-library/blocks/nextpage/editor.css 395 B
build/block-library/blocks/page-list/editor-rtl.css 363 B
build/block-library/blocks/page-list/editor.css 363 B
build/block-library/blocks/page-list/style-rtl.css 175 B
build/block-library/blocks/page-list/style.css 175 B
build/block-library/blocks/paragraph/editor-rtl.css 157 B
build/block-library/blocks/paragraph/editor.css 157 B
build/block-library/blocks/paragraph/style-rtl.css 260 B
build/block-library/blocks/paragraph/style.css 260 B
build/block-library/blocks/post-author/style-rtl.css 175 B
build/block-library/blocks/post-author/style.css 176 B
build/block-library/blocks/post-comments-form/editor-rtl.css 96 B
build/block-library/blocks/post-comments-form/editor.css 96 B
build/block-library/blocks/post-comments-form/style-rtl.css 493 B
build/block-library/blocks/post-comments-form/style.css 493 B
build/block-library/blocks/post-excerpt/editor-rtl.css 73 B
build/block-library/blocks/post-excerpt/editor.css 73 B
build/block-library/blocks/post-excerpt/style-rtl.css 69 B
build/block-library/blocks/post-excerpt/style.css 69 B
build/block-library/blocks/post-featured-image/editor-rtl.css 605 B
build/block-library/blocks/post-featured-image/editor.css 605 B
build/block-library/blocks/post-featured-image/style-rtl.css 153 B
build/block-library/blocks/post-featured-image/style.css 153 B
build/block-library/blocks/post-template/editor-rtl.css 99 B
build/block-library/blocks/post-template/editor.css 98 B
build/block-library/blocks/post-template/style-rtl.css 282 B
build/block-library/blocks/post-template/style.css 282 B
build/block-library/blocks/post-terms/style-rtl.css 73 B
build/block-library/blocks/post-terms/style.css 73 B
build/block-library/blocks/post-title/style-rtl.css 80 B
build/block-library/blocks/post-title/style.css 80 B
build/block-library/blocks/preformatted/style-rtl.css 103 B
build/block-library/blocks/preformatted/style.css 103 B
build/block-library/blocks/pullquote/editor-rtl.css 198 B
build/block-library/blocks/pullquote/editor.css 198 B
build/block-library/blocks/pullquote/style-rtl.css 370 B
build/block-library/blocks/pullquote/style.css 370 B
build/block-library/blocks/pullquote/theme-rtl.css 167 B
build/block-library/blocks/pullquote/theme.css 167 B
build/block-library/blocks/query-pagination-numbers/editor-rtl.css 122 B
build/block-library/blocks/query-pagination-numbers/editor.css 121 B
build/block-library/blocks/query-pagination/editor-rtl.css 221 B
build/block-library/blocks/query-pagination/editor.css 211 B
build/block-library/blocks/query-pagination/style-rtl.css 234 B
build/block-library/blocks/query-pagination/style.css 231 B
build/block-library/blocks/query/editor-rtl.css 365 B
build/block-library/blocks/query/editor.css 364 B
build/block-library/blocks/quote/style-rtl.css 213 B
build/block-library/blocks/quote/style.css 213 B
build/block-library/blocks/quote/theme-rtl.css 223 B
build/block-library/blocks/quote/theme.css 226 B
build/block-library/blocks/read-more/style-rtl.css 132 B
build/block-library/blocks/read-more/style.css 132 B
build/block-library/blocks/rss/editor-rtl.css 202 B
build/block-library/blocks/rss/editor.css 204 B
build/block-library/blocks/rss/style-rtl.css 289 B
build/block-library/blocks/rss/style.css 288 B
build/block-library/blocks/search/editor-rtl.css 165 B
build/block-library/blocks/search/editor.css 165 B
build/block-library/blocks/search/style-rtl.css 385 B
build/block-library/blocks/search/style.css 386 B
build/block-library/blocks/search/theme-rtl.css 114 B
build/block-library/blocks/search/theme.css 114 B
build/block-library/blocks/separator/editor-rtl.css 146 B
build/block-library/blocks/separator/editor.css 146 B
build/block-library/blocks/separator/style-rtl.css 233 B
build/block-library/blocks/separator/style.css 233 B
build/block-library/blocks/separator/theme-rtl.css 194 B
build/block-library/blocks/separator/theme.css 194 B
build/block-library/blocks/shortcode/editor-rtl.css 464 B
build/block-library/blocks/shortcode/editor.css 464 B
build/block-library/blocks/site-logo/editor-rtl.css 708 B
build/block-library/blocks/site-logo/editor.css 708 B
build/block-library/blocks/site-logo/style-rtl.css 192 B
build/block-library/blocks/site-logo/style.css 192 B
build/block-library/blocks/site-tagline/editor-rtl.css 86 B
build/block-library/blocks/site-tagline/editor.css 86 B
build/block-library/blocks/site-title/editor-rtl.css 84 B
build/block-library/blocks/site-title/editor.css 84 B
build/block-library/blocks/social-link/editor-rtl.css 184 B
build/block-library/blocks/social-link/editor.css 184 B
build/block-library/blocks/social-links/editor-rtl.css 674 B
build/block-library/blocks/social-links/editor.css 673 B
build/block-library/blocks/social-links/style-rtl.css 1.39 kB
build/block-library/blocks/social-links/style.css 1.38 kB
build/block-library/blocks/spacer/editor-rtl.css 322 B
build/block-library/blocks/spacer/editor.css 322 B
build/block-library/blocks/spacer/style-rtl.css 48 B
build/block-library/blocks/spacer/style.css 48 B
build/block-library/blocks/table/editor-rtl.css 494 B
build/block-library/blocks/table/editor.css 494 B
build/block-library/blocks/table/style-rtl.css 611 B
build/block-library/blocks/table/style.css 609 B
build/block-library/blocks/table/theme-rtl.css 175 B
build/block-library/blocks/table/theme.css 175 B
build/block-library/blocks/tag-cloud/style-rtl.css 226 B
build/block-library/blocks/tag-cloud/style.css 227 B
build/block-library/blocks/template-part/editor-rtl.css 235 B
build/block-library/blocks/template-part/editor.css 235 B
build/block-library/blocks/template-part/theme-rtl.css 101 B
build/block-library/blocks/template-part/theme.css 101 B
build/block-library/blocks/text-columns/editor-rtl.css 95 B
build/block-library/blocks/text-columns/editor.css 95 B
build/block-library/blocks/text-columns/style-rtl.css 166 B
build/block-library/blocks/text-columns/style.css 166 B
build/block-library/blocks/verse/style-rtl.css 87 B
build/block-library/blocks/verse/style.css 87 B
build/block-library/blocks/video/editor-rtl.css 561 B
build/block-library/blocks/video/editor.css 563 B
build/block-library/blocks/video/style-rtl.css 159 B
build/block-library/blocks/video/style.css 159 B
build/block-library/blocks/video/theme-rtl.css 110 B
build/block-library/blocks/video/theme.css 110 B
build/block-library/common-rtl.css 1.01 kB
build/block-library/common.css 1 kB
build/block-library/editor-elements-rtl.css 75 B
build/block-library/editor-elements.css 75 B
build/block-library/editor-rtl.css 10.9 kB
build/block-library/editor.css 10.9 kB
build/block-library/elements-rtl.css 54 B
build/block-library/elements.css 54 B
build/block-library/index.min.js 184 kB
build/block-library/reset-rtl.css 478 B
build/block-library/reset.css 478 B
build/block-library/style-rtl.css 11.8 kB
build/block-library/style.css 11.8 kB
build/block-library/theme-rtl.css 695 B
build/block-library/theme.css 700 B
build/block-serialization-default-parser/index.min.js 1.11 kB
build/block-serialization-spec-parser/index.min.js 2.83 kB
build/blocks/index.min.js 47.2 kB
build/components/style-rtl.css 14 kB
build/components/style.css 14 kB
build/compose/index.min.js 11.7 kB
build/core-data/index.min.js 14.7 kB
build/customize-widgets/index.min.js 11.2 kB
build/customize-widgets/style-rtl.css 1.4 kB
build/customize-widgets/style.css 1.4 kB
build/data-controls/index.min.js 653 B
build/data/index.min.js 7.99 kB
build/date/index.min.js 32 kB
build/deprecated/index.min.js 507 B
build/dom-ready/index.min.js 324 B
build/dom/index.min.js 4.69 kB
build/edit-navigation/index.min.js 16 kB
build/edit-navigation/style-rtl.css 4.02 kB
build/edit-navigation/style.css 4.03 kB
build/edit-post/classic-rtl.css 546 B
build/edit-post/classic.css 547 B
build/edit-post/index.min.js 30.5 kB
build/edit-post/style-rtl.css 6.94 kB
build/edit-post/style.css 6.94 kB
build/edit-site/index.min.js 55.1 kB
build/edit-site/style-rtl.css 8.21 kB
build/edit-site/style.css 8.19 kB
build/edit-widgets/index.min.js 16.5 kB
build/edit-widgets/style-rtl.css 4.35 kB
build/edit-widgets/style.css 4.35 kB
build/editor/index.min.js 41.3 kB
build/editor/style-rtl.css 3.66 kB
build/editor/style.css 3.65 kB
build/element/index.min.js 4.27 kB
build/escape-html/index.min.js 537 B
build/format-library/index.min.js 6.75 kB
build/format-library/style-rtl.css 571 B
build/format-library/style.css 571 B
build/hooks/index.min.js 1.64 kB
build/html-entities/index.min.js 448 B
build/i18n/index.min.js 3.77 kB
build/is-shallow-equal/index.min.js 527 B
build/keyboard-shortcuts/index.min.js 1.78 kB
build/keycodes/index.min.js 1.39 kB
build/list-reusable-blocks/index.min.js 1.74 kB
build/list-reusable-blocks/style-rtl.css 835 B
build/list-reusable-blocks/style.css 835 B
build/media-utils/index.min.js 2.93 kB
build/notices/index.min.js 953 B
build/nux/index.min.js 2.05 kB
build/nux/style-rtl.css 732 B
build/nux/style.css 728 B
build/plugins/index.min.js 1.94 kB
build/preferences-persistence/index.min.js 2.22 kB
build/preferences/index.min.js 1.3 kB
build/primitives/index.min.js 933 B
build/priority-queue/index.min.js 612 B
build/react-i18n/index.min.js 696 B
build/react-refresh-entry/index.min.js 8.44 kB
build/react-refresh-runtime/index.min.js 7.31 kB
build/redux-routine/index.min.js 2.68 kB
build/reusable-blocks/index.min.js 2.22 kB
build/reusable-blocks/style-rtl.css 256 B
build/reusable-blocks/style.css 256 B
build/rich-text/index.min.js 11.1 kB
build/server-side-render/index.min.js 1.61 kB
build/shortcode/index.min.js 1.53 kB
build/token-list/index.min.js 644 B
build/url/index.min.js 3.61 kB
build/vendors/react-dom.min.js 38.5 kB
build/vendors/react.min.js 4.34 kB
build/viewport/index.min.js 1.08 kB
build/warning/index.min.js 268 B
build/widgets/index.min.js 7.19 kB
build/widgets/style-rtl.css 1.16 kB
build/widgets/style.css 1.16 kB
build/wordcount/index.min.js 1.06 kB

compressed-size-action

@stokesman stokesman marked this pull request as ready for review July 17, 2022 21:17
@stokesman stokesman requested a review from ajitbohra as a code owner July 17, 2022 21:17
Copy link
Contributor

@aaronrobertshaw aaronrobertshaw 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 the quick turnaround and fix @stokesman 🚀

✅ Updated unit test fails as expected on initial commit on the branch
✅ Could still replicate the issue in both Storybook & editors
✅ After checking out latest on PR branch the unit tests pass
✅ Fix resolves the original issue in the editor & storybook.
✅ Components are still functioning in Storybook

Before After
Screen.Recording.2022-07-18.at.10.26.09.am.mp4
Screen.Recording.2022-07-18.at.10.45.56.am.mp4

LGTM!

@ciampo ciampo added [Type] Bug An existing feature does not function as intended [Feature] UI Components Impacts or related to the UI component system labels Jul 18, 2022
@ciampo
Copy link
Contributor

ciampo commented Jul 18, 2022

Would it make sense to add a unit test to avoid future regressions?

@stokesman
Copy link
Contributor Author

That was why I tweaked the test in eb9e1bc. I think it suffices but let me know if you see a fault with that.

Copy link
Contributor

@ciampo ciampo left a comment

Choose a reason for hiding this comment

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

Thank you for the reply — I had missed that small change in my first pass of this PR.

With regards to the changes in this PR, it would be good if we could find an approach that preserves the reducer's "purity" (i.e. accepting action objects).

Since you mentioned this snippet related to the dispatch RESET action

case actions.RESET:
nextState.error = null;
nextState.isDirty = false;
nextState.value = action.payload.value || state.initialValue;

Wouldn't be possible to use the null coalescing operator ?? to let falsy values through?

nextState.value = action.payload.value ?? state.initialValue;

@stokesman
Copy link
Contributor Author

Wouldn't be possible to use the null coalescing operator ?? to let falsy values through?
nextState.value = action.payload.value ?? state.initialValue;

That would work for empty strings or 0 but I think in the case of BoxControl it's actually sending undefined on the reset. Other than that you might recall the concern I had with reusing RESET #40518 (comment). So it seemed an opportune time to also address that.

it would be good if we could find an approach that preserves the reducer's "purity" (i.e. accepting action objects).

We can add another action (we used to have UPDATE for this). I don't see it as having any significant advantage or disadvantage compared this branch as it is now.

@ciampo ciampo requested a review from mirka July 20, 2022 06:53
@ciampo
Copy link
Contributor

ciampo commented Jul 20, 2022

We can add another action (we used to have UPDATE for this).

That would my preferred solution — if the UDPATE action is going to be used for controlled updates for props, then we could rename it to CONTROLLED_UPDATE (or at least add a comment explaining what makes that action different from CHANGE, for example)

I don't see it as having any significant advantage or disadvantage compared this branch as it is now.

The advantage that I see is that we would keep inputControlStateReducer as a canonical reducer function (i.e. a function that receives the current state and an action object, decides how to update the state if necessary, and returns the new state)

@mirka
Copy link
Member

mirka commented Jul 20, 2022

The advantage that I see is that we would keep inputControlStateReducer as a canonical reducer function (i.e. a function that receives the current state and an action object

Same. If there are no significant tradeoffs, I would prefer an explicit action type. At least as a casual code reader, I would expect there to always be a type.

@stokesman stokesman force-pushed the fix/reset-of-input-control-based-components branch from 5dd5fd6 to 633709a Compare July 21, 2022 16:19
@stokesman stokesman changed the title Fix clearing of InputControl-based components InputControl: Fix acceptance of falsy values in controlled updates Jul 21, 2022
Copy link
Contributor

@ciampo ciampo left a comment

Choose a reason for hiding this comment

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

Changes test well — I just left a few more comments, but this is looking close!

@@ -19,7 +19,7 @@ export interface InputState {
value?: string;
}

export type StateReducer = Reducer< InputState, InputAction >;
export type StateReducer = Reducer< InputState, InputAction | ControlAction >;
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't the ControlAction be part of the InputAction type ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can see why you'd ask. Looking at this more closely, given we are okay with the way I've wired up CONTROL to be exclusively handled by the base reducer, then I'll say no, and yet it's still not quite right as is. The extending reducers will never receive CONTROL actions so StateReducer would be too loose a type for those.

I'm thinking we should we keep ControlAction out of both InputAction and StateReducer then make only the base reducer typed to accept it.
diff --git a/packages/components/src/input-control/reducer/reducer.ts b/packages/components/src/input-control/reducer/reducer.ts
index 3f3302608d..a5f80026a6 100644
--- a/packages/components/src/input-control/reducer/reducer.ts
+++ b/packages/components/src/input-control/reducer/reducer.ts
@@ -1,7 +1,7 @@
 /**
  * External dependencies
  */
-import type { SyntheticEvent, ChangeEvent, PointerEvent } from 'react';
+import type { SyntheticEvent, ChangeEvent, PointerEvent, Reducer } from 'react';
 
 /**
  * WordPress dependencies
@@ -50,7 +50,7 @@ function mergeInitialState(
  */
 function inputControlStateReducer(
 	composedStateReducers: StateReducer
-): StateReducer {
+): Reducer< InputState, actions.InputAction | actions.ControlAction > {
 	return ( state, action ) => {
 		const nextState = { ...state };
 
@@ -151,7 +151,7 @@ export function useInputControlStateReducer(
 	initialState: Partial< InputState > = initialInputControlState,
 	onChangeHandler: InputChangeCallback
 ) {
-	const [ state, dispatch ] = useReducer< StateReducer >(
+	const [ state, dispatch ] = useReducer(
 		inputControlStateReducer( stateReducer ),
 		mergeInitialState( initialState )
 	);
diff --git a/packages/components/src/input-control/reducer/state.ts b/packages/components/src/input-control/reducer/state.ts
index 53a49aa765..2ca9edaa27 100644
--- a/packages/components/src/input-control/reducer/state.ts
+++ b/packages/components/src/input-control/reducer/state.ts
@@ -6,7 +6,7 @@ import type { Reducer, SyntheticEvent } from 'react';
 /**
  * Internal dependencies
  */
-import type { ControlAction, InputAction } from './actions';
+import type { InputAction } from './actions';
 
 export interface InputState {
 	_event?: SyntheticEvent;
@@ -19,7 +19,7 @@ export interface InputState {
 	value?: string;
 }
 
-export type StateReducer = Reducer< InputState, InputAction | ControlAction >;
+export type StateReducer = Reducer< InputState, InputAction >;
 
 export const initialStateReducer: StateReducer = ( state: InputState ) => state;
 

Copy link
Contributor

Choose a reason for hiding this comment

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

The approach that you propose sounds reasonable — maybe we could experiment with generics in the StateReducer type, so that we keep all type defs in the same file?

Something like this
diff --git a/packages/components/src/input-control/reducer/reducer.ts b/packages/components/src/input-control/reducer/reducer.ts
index 3f3302608d..c41ae85775 100644
--- a/packages/components/src/input-control/reducer/reducer.ts
+++ b/packages/components/src/input-control/reducer/reducer.ts
@@ -50,7 +50,7 @@ function mergeInitialState(
  */
 function inputControlStateReducer(
 	composedStateReducers: StateReducer
-): StateReducer {
+): StateReducer< actions.ControlAction > {
 	return ( state, action ) => {
 		const nextState = { ...state };
 
@@ -151,7 +151,7 @@ export function useInputControlStateReducer(
 	initialState: Partial< InputState > = initialInputControlState,
 	onChangeHandler: InputChangeCallback
 ) {
-	const [ state, dispatch ] = useReducer< StateReducer >(
+	const [ state, dispatch ] = useReducer(
 		inputControlStateReducer( stateReducer ),
 		mergeInitialState( initialState )
 	);
diff --git a/packages/components/src/input-control/reducer/state.ts b/packages/components/src/input-control/reducer/state.ts
index 53a49aa765..839ac51383 100644
--- a/packages/components/src/input-control/reducer/state.ts
+++ b/packages/components/src/input-control/reducer/state.ts
@@ -6,7 +6,7 @@ import type { Reducer, SyntheticEvent } from 'react';
 /**
  * Internal dependencies
  */
-import type { ControlAction, InputAction } from './actions';
+import type { InputAction } from './actions';
 
 export interface InputState {
 	_event?: SyntheticEvent;
@@ -19,7 +19,10 @@ export interface InputState {
 	value?: string;
 }
 
-export type StateReducer = Reducer< InputState, InputAction | ControlAction >;
+export type StateReducer< TAdditionalAction = {} > = Reducer<
+	InputState,
+	InputAction | TAdditionalAction
+>;
 
 export const initialStateReducer: StateReducer = ( state: InputState ) => state;
 

We should probably also add a comment about inputControlStateReducer being the only reducer exposing / handling the CONTROL action — which means that it's an action that doesn't get exposed to external reducer middleware, meant to be used only internally. Maybe in the inputControlStateReducer's JSDocs ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in 9aef4eb though it required a touch of the Action type as well. At first I didn't favor the idea generalizing the StateReducer type for a singular exception to its shape but given that we're interested in refactoring to a hook+component combo #41264 (comment) there will be opportunity make more use of the generic.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

…add a comment about inputControlStateReducer being the only reducer exposing / handling the CONTROL action

Done in f394357

packages/components/src/input-control/reducer/reducer.ts Outdated Show resolved Hide resolved
@stokesman
Copy link
Contributor Author

stokesman commented Jul 27, 2022

Feedback is hopefully addressed satisfactorily. In case not, I've left the rebase/conflict pending for the moment.

Copy link
Contributor

@ciampo ciampo left a comment

Choose a reason for hiding this comment

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

Feedback is hopefully addressed satisfactorily. In case not, I've left the rebase/conflict pending for the moment.

I left one last comment, but the changes required should not add runtime changes to this PR.

When that's addressed, we can rebase and give this PR a final check before merging :)

Comment on lines 22 to 27
export type StateReducer< SpecializedAction = Action > = Reducer<
InputState,
InputAction | SpecializedAction
>;
Copy link
Contributor

Choose a reason for hiding this comment

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

In the current state of this PR, the StateReducer type would accept any action-like object (this can be verified by inspecting action.type and seeing what possible values are attributed to it by the TypeScript engine).

Instead, what we'd like to achieve is:

  • When SpecializedAction is not defined, StateReducer should only allow actions from the InputActions type
  • When SpecializedAction is defined, StateReducer should only allow actions from either the InputActions type, or the SpecializedAction type (although we need to make sure that SpecializedAction is a valid Action).

I played around with code a bit and I think this should be a good solution:

Suggested change
export type StateReducer< SpecializedAction = Action > = Reducer<
InputState,
InputAction | SpecializedAction
>;
export type StateReducer< SpecializedAction = {} > = Reducer<
InputState,
SpecializedAction extends Action
? InputAction | SpecializedAction
: InputAction
>;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Very neat Marco! Applied in 30d7c31. I don't recall having seen such logic in typing. TIL 🙌

Copy link
Contributor

Choose a reason for hiding this comment

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

Thank you! I learnt this technique by reading the definition of the WordPressComponentProps type

( IsPolymorphic extends true
? {
/** The HTML element or React component to render the component as. */
as?: T | keyof JSX.IntrinsicElements;
}
: {} );

stokesman and others added 3 commits July 28, 2022 08:59
Co-Authored-By: Marco Ciampini <1083581+ciampo@users.noreply.github.com>
@stokesman stokesman force-pushed the fix/reset-of-input-control-based-components branch from 3b54cff to 30d7c31 Compare July 28, 2022 16:00
Copy link
Contributor

@ciampo ciampo left a comment

Choose a reason for hiding this comment

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

LGTM 🚀

Thank you as always for your work, @stokesman !

@stokesman
Copy link
Contributor Author

Thanks for testing and reviewing Aaron and Marco 🙇

@stokesman stokesman merged commit fba021a into trunk Jul 29, 2022
@stokesman stokesman deleted the fix/reset-of-input-control-based-components branch July 29, 2022 15:29
@github-actions github-actions bot added this to the Gutenberg 13.9 milestone Jul 29, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] UI Components Impacts or related to the UI component system [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.

Inspector Controls: Inputs aren't clearing after control is reset via panel menu
4 participants