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

Remove value syncing in Link Control #51387

Merged
merged 5 commits into from
Oct 9, 2023

Conversation

getdave
Copy link
Contributor

@getdave getdave commented Jun 9, 2023

What?

Removes the (potentially) unwanted value syncing in Link Control component.

Closes #51256

Why?

It might be unnecessary as per #51256

How?

Remove the effectful code.

Testing Instructions

Run the tests with

npm run test:unit packages/block-editor/src/components/link-control/

Testing Instructions for Keyboard

Screenshots or screencast

@getdave getdave added [Type] Code Quality Issues or PRs that relate to code quality [Feature] Link Editing Link components (LinkControl, URLInput) and integrations (RichText link formatting) labels Jun 9, 2023
@getdave getdave requested a review from Mamaduka June 9, 2023 15:53
@getdave getdave self-assigned this Jun 9, 2023
@getdave
Copy link
Contributor Author

getdave commented Jun 9, 2023

@Mamaduka if you look at the scenario in:

npm run test:unit packages/block-editor/src/components/link-control/ -- --t "should reset state upon controlled value change"

...it should outline why this code exists. Let's see if we can work out a way to remove it.

For example in rich text we use a key to force remount:

<LinkControl
key={ forceRemountKey }
value={ linkValue }
onChange={ onChangeLink }

@github-actions
Copy link

github-actions bot commented Jun 9, 2023

Size Change: +47 B (0%)

Total Size: 1.65 MB

Filename Size Change
build/block-editor/index.min.js 218 kB +47 B (0%)
ℹ️ View Unchanged
Filename Size
build/a11y/index.min.js 964 B
build/annotations/index.min.js 2.71 kB
build/api-fetch/index.min.js 2.29 kB
build/autop/index.min.js 2.11 kB
build/blob/index.min.js 461 B
build/block-directory/index.min.js 7.07 kB
build/block-directory/style-rtl.css 1.04 kB
build/block-directory/style.css 1.04 kB
build/block-editor/content-rtl.css 4.28 kB
build/block-editor/content.css 4.27 kB
build/block-editor/default-editor-styles-rtl.css 403 B
build/block-editor/default-editor-styles.css 403 B
build/block-editor/style-rtl.css 15.6 kB
build/block-editor/style.css 15.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 90 B
build/block-library/blocks/archives/style.css 90 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 122 B
build/block-library/blocks/audio/style.css 122 B
build/block-library/blocks/audio/theme-rtl.css 138 B
build/block-library/blocks/audio/theme.css 138 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 104 B
build/block-library/blocks/avatar/style.css 104 B
build/block-library/blocks/block/editor-rtl.css 305 B
build/block-library/blocks/block/editor.css 305 B
build/block-library/blocks/button/editor-rtl.css 587 B
build/block-library/blocks/button/editor.css 587 B
build/block-library/blocks/button/style-rtl.css 633 B
build/block-library/blocks/button/style.css 632 B
build/block-library/blocks/buttons/editor-rtl.css 337 B
build/block-library/blocks/buttons/editor.css 337 B
build/block-library/blocks/buttons/style-rtl.css 332 B
build/block-library/blocks/buttons/style.css 332 B
build/block-library/blocks/calendar/style-rtl.css 239 B
build/block-library/blocks/calendar/style.css 239 B
build/block-library/blocks/categories/editor-rtl.css 113 B
build/block-library/blocks/categories/editor.css 112 B
build/block-library/blocks/categories/style-rtl.css 124 B
build/block-library/blocks/categories/style.css 124 B
build/block-library/blocks/code/editor-rtl.css 53 B
build/block-library/blocks/code/editor.css 53 B
build/block-library/blocks/code/style-rtl.css 121 B
build/block-library/blocks/code/style.css 121 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 421 B
build/block-library/blocks/columns/style.css 421 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 199 B
build/block-library/blocks/comment-template/style.css 198 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 840 B
build/block-library/blocks/comments/editor.css 839 B
build/block-library/blocks/comments/style-rtl.css 637 B
build/block-library/blocks/comments/style.css 636 B
build/block-library/blocks/cover/editor-rtl.css 647 B
build/block-library/blocks/cover/editor.css 650 B
build/block-library/blocks/cover/style-rtl.css 1.7 kB
build/block-library/blocks/cover/style.css 1.69 kB
build/block-library/blocks/details/editor-rtl.css 65 B
build/block-library/blocks/details/editor.css 65 B
build/block-library/blocks/details/style-rtl.css 98 B
build/block-library/blocks/details/style.css 98 B
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 138 B
build/block-library/blocks/embed/theme.css 138 B
build/block-library/blocks/file/editor-rtl.css 316 B
build/block-library/blocks/file/editor.css 316 B
build/block-library/blocks/file/style-rtl.css 311 B
build/block-library/blocks/file/style.css 312 B
build/block-library/blocks/file/view.min.js 321 B
build/block-library/blocks/footnotes/style-rtl.css 201 B
build/block-library/blocks/footnotes/style.css 199 B
build/block-library/blocks/form-input/editor-rtl.css 229 B
build/block-library/blocks/form-input/editor.css 228 B
build/block-library/blocks/form-input/style-rtl.css 343 B
build/block-library/blocks/form-input/style.css 343 B
build/block-library/blocks/form-submission-notification/editor-rtl.css 343 B
build/block-library/blocks/form-submission-notification/editor.css 342 B
build/block-library/blocks/form-submit-button/style-rtl.css 69 B
build/block-library/blocks/form-submit-button/style.css 69 B
build/block-library/blocks/form/view.min.js 452 B
build/block-library/blocks/freeform/editor-rtl.css 2.61 kB
build/block-library/blocks/freeform/editor.css 2.61 kB
build/block-library/blocks/gallery/editor-rtl.css 957 B
build/block-library/blocks/gallery/editor.css 962 B
build/block-library/blocks/gallery/style-rtl.css 1.55 kB
build/block-library/blocks/gallery/style.css 1.55 kB
build/block-library/blocks/gallery/theme-rtl.css 122 B
build/block-library/blocks/gallery/theme.css 122 B
build/block-library/blocks/group/editor-rtl.css 654 B
build/block-library/blocks/group/editor.css 654 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 189 B
build/block-library/blocks/heading/style.css 189 B
build/block-library/blocks/html/editor-rtl.css 340 B
build/block-library/blocks/html/editor.css 341 B
build/block-library/blocks/image/editor-rtl.css 834 B
build/block-library/blocks/image/editor.css 833 B
build/block-library/blocks/image/style-rtl.css 1.45 kB
build/block-library/blocks/image/style.css 1.44 kB
build/block-library/blocks/image/theme-rtl.css 137 B
build/block-library/blocks/image/theme.css 137 B
build/block-library/blocks/image/view.min.js 1.83 kB
build/block-library/blocks/latest-comments/style-rtl.css 357 B
build/block-library/blocks/latest-comments/style.css 357 B
build/block-library/blocks/latest-posts/editor-rtl.css 213 B
build/block-library/blocks/latest-posts/editor.css 212 B
build/block-library/blocks/latest-posts/style-rtl.css 478 B
build/block-library/blocks/latest-posts/style.css 478 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 505 B
build/block-library/blocks/media-text/style.css 503 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 671 B
build/block-library/blocks/navigation-link/editor.css 672 B
build/block-library/blocks/navigation-link/style-rtl.css 103 B
build/block-library/blocks/navigation-link/style.css 103 B
build/block-library/blocks/navigation-submenu/editor-rtl.css 299 B
build/block-library/blocks/navigation-submenu/editor.css 299 B
build/block-library/blocks/navigation/editor-rtl.css 2.26 kB
build/block-library/blocks/navigation/editor.css 2.26 kB
build/block-library/blocks/navigation/style-rtl.css 2.26 kB
build/block-library/blocks/navigation/style.css 2.25 kB
build/block-library/blocks/navigation/view.min.js 1.01 kB
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 401 B
build/block-library/blocks/page-list/editor.css 401 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 235 B
build/block-library/blocks/paragraph/editor.css 235 B
build/block-library/blocks/paragraph/style-rtl.css 335 B
build/block-library/blocks/paragraph/style.css 335 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 508 B
build/block-library/blocks/post-comments-form/style.css 508 B
build/block-library/blocks/post-date/style-rtl.css 61 B
build/block-library/blocks/post-date/style.css 61 B
build/block-library/blocks/post-excerpt/editor-rtl.css 71 B
build/block-library/blocks/post-excerpt/editor.css 71 B
build/block-library/blocks/post-excerpt/style-rtl.css 141 B
build/block-library/blocks/post-excerpt/style.css 141 B
build/block-library/blocks/post-featured-image/editor-rtl.css 588 B
build/block-library/blocks/post-featured-image/editor.css 586 B
build/block-library/blocks/post-featured-image/style-rtl.css 345 B
build/block-library/blocks/post-featured-image/style.css 345 B
build/block-library/blocks/post-navigation-link/style-rtl.css 215 B
build/block-library/blocks/post-navigation-link/style.css 214 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 409 B
build/block-library/blocks/post-template/style.css 408 B
build/block-library/blocks/post-terms/style-rtl.css 96 B
build/block-library/blocks/post-terms/style.css 96 B
build/block-library/blocks/post-time-to-read/style-rtl.css 69 B
build/block-library/blocks/post-time-to-read/style.css 69 B
build/block-library/blocks/post-title/style-rtl.css 100 B
build/block-library/blocks/post-title/style.css 100 B
build/block-library/blocks/preformatted/style-rtl.css 125 B
build/block-library/blocks/preformatted/style.css 125 B
build/block-library/blocks/pullquote/editor-rtl.css 135 B
build/block-library/blocks/pullquote/editor.css 135 B
build/block-library/blocks/pullquote/style-rtl.css 335 B
build/block-library/blocks/pullquote/style.css 335 B
build/block-library/blocks/pullquote/theme-rtl.css 168 B
build/block-library/blocks/pullquote/theme.css 168 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 288 B
build/block-library/blocks/query-pagination/style.css 284 B
build/block-library/blocks/query-title/style-rtl.css 63 B
build/block-library/blocks/query-title/style.css 63 B
build/block-library/blocks/query/editor-rtl.css 486 B
build/block-library/blocks/query/editor.css 486 B
build/block-library/blocks/query/style-rtl.css 375 B
build/block-library/blocks/query/style.css 372 B
build/block-library/blocks/query/view.min.js 609 B
build/block-library/blocks/quote/style-rtl.css 222 B
build/block-library/blocks/quote/style.css 222 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 149 B
build/block-library/blocks/rss/editor.css 149 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 184 B
build/block-library/blocks/search/editor.css 184 B
build/block-library/blocks/search/style-rtl.css 613 B
build/block-library/blocks/search/style.css 613 B
build/block-library/blocks/search/theme-rtl.css 114 B
build/block-library/blocks/search/theme.css 114 B
build/block-library/blocks/search/view.min.js 471 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 234 B
build/block-library/blocks/separator/style.css 234 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 329 B
build/block-library/blocks/shortcode/editor.css 329 B
build/block-library/blocks/site-logo/editor-rtl.css 760 B
build/block-library/blocks/site-logo/editor.css 760 B
build/block-library/blocks/site-logo/style-rtl.css 204 B
build/block-library/blocks/site-logo/style.css 204 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 116 B
build/block-library/blocks/site-title/editor.css 116 B
build/block-library/blocks/site-title/style-rtl.css 57 B
build/block-library/blocks/site-title/style.css 57 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 682 B
build/block-library/blocks/social-links/editor.css 681 B
build/block-library/blocks/social-links/style-rtl.css 1.45 kB
build/block-library/blocks/social-links/style.css 1.45 kB
build/block-library/blocks/spacer/editor-rtl.css 359 B
build/block-library/blocks/spacer/editor.css 359 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 432 B
build/block-library/blocks/table/editor.css 432 B
build/block-library/blocks/table/style-rtl.css 646 B
build/block-library/blocks/table/style.css 645 B
build/block-library/blocks/table/theme-rtl.css 157 B
build/block-library/blocks/table/theme.css 157 B
build/block-library/blocks/tag-cloud/style-rtl.css 251 B
build/block-library/blocks/tag-cloud/style.css 253 B
build/block-library/blocks/template-part/editor-rtl.css 403 B
build/block-library/blocks/template-part/editor.css 403 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/term-description/style-rtl.css 111 B
build/block-library/blocks/term-description/style.css 111 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 99 B
build/block-library/blocks/verse/style.css 99 B
build/block-library/blocks/video/editor-rtl.css 552 B
build/block-library/blocks/video/editor.css 555 B
build/block-library/blocks/video/style-rtl.css 191 B
build/block-library/blocks/video/style.css 191 B
build/block-library/blocks/video/theme-rtl.css 139 B
build/block-library/blocks/video/theme.css 139 B
build/block-library/classic-rtl.css 179 B
build/block-library/classic.css 179 B
build/block-library/common-rtl.css 1.11 kB
build/block-library/common.css 1.11 kB
build/block-library/editor-elements-rtl.css 75 B
build/block-library/editor-elements.css 75 B
build/block-library/editor-rtl.css 12.4 kB
build/block-library/editor.css 12.4 kB
build/block-library/elements-rtl.css 54 B
build/block-library/elements.css 54 B
build/block-library/index.min.js 211 kB
build/block-library/reset-rtl.css 472 B
build/block-library/reset.css 472 B
build/block-library/style-rtl.css 14.3 kB
build/block-library/style.css 14.3 kB
build/block-library/theme-rtl.css 700 B
build/block-library/theme.css 705 B
build/block-serialization-default-parser/index.min.js 1.13 kB
build/block-serialization-spec-parser/index.min.js 2.87 kB
build/blocks/index.min.js 51.5 kB
build/commands/index.min.js 15.5 kB
build/commands/style-rtl.css 947 B
build/commands/style.css 942 B
build/components/index.min.js 248 kB
build/components/style-rtl.css 11.9 kB
build/components/style.css 11.9 kB
build/compose/index.min.js 12.7 kB
build/core-commands/index.min.js 2.72 kB
build/core-data/index.min.js 70.5 kB
build/customize-widgets/index.min.js 12 kB
build/customize-widgets/style-rtl.css 1.51 kB
build/customize-widgets/style.css 1.5 kB
build/data-controls/index.min.js 651 B
build/data/index.min.js 8.78 kB
build/date/index.min.js 17.9 kB
build/deprecated/index.min.js 462 B
build/dom-ready/index.min.js 336 B
build/dom/index.min.js 4.68 kB
build/edit-post/classic-rtl.css 571 B
build/edit-post/classic.css 571 B
build/edit-post/index.min.js 35.6 kB
build/edit-post/style-rtl.css 7.89 kB
build/edit-post/style.css 7.88 kB
build/edit-site/index.min.js 203 kB
build/edit-site/style-rtl.css 14.1 kB
build/edit-site/style.css 14.1 kB
build/edit-widgets/index.min.js 17 kB
build/edit-widgets/style-rtl.css 4.84 kB
build/edit-widgets/style.css 4.84 kB
build/editor/index.min.js 45.9 kB
build/editor/style-rtl.css 3.58 kB
build/editor/style.css 3.58 kB
build/element/index.min.js 4.87 kB
build/escape-html/index.min.js 548 B
build/format-library/index.min.js 7.79 kB
build/format-library/style-rtl.css 577 B
build/format-library/style.css 577 B
build/hooks/index.min.js 1.57 kB
build/html-entities/index.min.js 454 B
build/i18n/index.min.js 3.61 kB
build/interactivity/index.min.js 11.4 kB
build/is-shallow-equal/index.min.js 535 B
build/keyboard-shortcuts/index.min.js 1.76 kB
build/keycodes/index.min.js 1.9 kB
build/list-reusable-blocks/index.min.js 2.21 kB
build/list-reusable-blocks/style-rtl.css 865 B
build/list-reusable-blocks/style.css 865 B
build/media-utils/index.min.js 2.92 kB
build/notices/index.min.js 964 B
build/nux/index.min.js 2.01 kB
build/nux/style-rtl.css 775 B
build/nux/style.css 771 B
build/patterns/index.min.js 3.57 kB
build/patterns/style-rtl.css 325 B
build/patterns/style.css 325 B
build/plugins/index.min.js 1.81 kB
build/preferences-persistence/index.min.js 1.85 kB
build/preferences/index.min.js 1.26 kB
build/primitives/index.min.js 994 B
build/priority-queue/index.min.js 1.52 kB
build/private-apis/index.min.js 972 B
build/react-i18n/index.min.js 631 B
build/react-refresh-entry/index.min.js 9.46 kB
build/react-refresh-runtime/index.min.js 6.78 kB
build/redux-routine/index.min.js 2.71 kB
build/reusable-blocks/index.min.js 2.73 kB
build/reusable-blocks/style-rtl.css 265 B
build/reusable-blocks/style.css 265 B
build/rich-text/index.min.js 10.2 kB
build/router/index.min.js 1.79 kB
build/server-side-render/index.min.js 1.96 kB
build/shortcode/index.min.js 1.4 kB
build/style-engine/index.min.js 1.98 kB
build/token-list/index.min.js 587 B
build/url/index.min.js 3.84 kB
build/vendors/inert-polyfill.min.js 2.48 kB
build/vendors/react-dom.min.js 41.8 kB
build/vendors/react.min.js 4.02 kB
build/viewport/index.min.js 967 B
build/warning/index.min.js 259 B
build/widgets/index.min.js 7.18 kB
build/widgets/style-rtl.css 1.18 kB
build/widgets/style.css 1.18 kB
build/wordcount/index.min.js 1.03 kB

compressed-size-action

Copy link
Member

@Mamaduka Mamaduka left a comment

Choose a reason for hiding this comment

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

The key is one of the recommended ways to reset the component state in React. So I don't see anything wrong with using it.

Maybe we just need to better document this method for LinkControl.

cc @kevin940726

@getdave
Copy link
Contributor Author

getdave commented Jul 26, 2023

@Mamaduka One approach we could consider here is exporting a wrapped version of LinkControl which has a key generated for you based on the value prop.

const LinkControlWithKey = ( props ) => {
	return (
		<LinkControl { ...props } key={ useLinkInstanceKey( props?.value ) } />
	);
};

export default LinkControlWithKey;

I tried this and it does seem to work. The hook comes from

function useLinkInstanceKey( instance ) {

I'm not 100% sure about the approach so I'd appreciate your 👀 (and also @kevin940726 if he is around?).

@Mamaduka
Copy link
Member

My suggestion would suggest not to use an implicit approach. Let the consumer components handle state resetting via key. The less "magic" is there inside the components, the easier it's to debug them :)

We can document the case in the readme file and link the React docs. What do you think?

@getdave
Copy link
Contributor Author

getdave commented Jul 26, 2023

My suggestion would suggest not to use an implicit approach. Let the consumer components handle state resetting via key. The less "magic" is there inside the components, the easier it's to debug them :)

We can document the case in the readme file and link the React docs. What do you think?

That's probably best. I just realised there's a problem with the automatic approach anyway. Ok so we remove it and update docs. I'll write the docs tomorrow UTC.

@getdave getdave requested review from kevin940726 and Mamaduka July 27, 2023 08:43
@getdave
Copy link
Contributor Author

getdave commented Jul 27, 2023

@Mamaduka I updated the docs. Let's refine them and see if it's good enough to merge now I removed the test coverage.

@Mamaduka
Copy link
Member

Thank you, @getdave! The update looks good (for me, personally) but also feels too technical. People who'll understand all the details there probably know about resetting the state via key.

Maybe we should just document how to solve this particular case and skip why it might happen.

P.S. I'm terrible at writing docs. Going to ping @ndiego and @ryanwelcher if they have any suggestions.

kevin940726
kevin940726 previously approved these changes Jul 27, 2023
Copy link
Member

@kevin940726 kevin940726 left a comment

Choose a reason for hiding this comment

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

I'm a bit confused as it's been a while since I looked at <LinkControl> so excuse me if I'm horribly wrong 😅 . Don't we already have key defined in

key={ value?.url } // force remount when URL changes to avoid race conditions for rich previews
? Is there a case where that's not enough? Should we just update the key to take the full value (rather than only the url` into account?

Either way I think this is still a better change. Nice job on updating the doc too! Perhaps we can also link to the official React doc about this for more info?

@getdave
Copy link
Contributor Author

getdave commented Jul 27, 2023

@kevin940726 Thanks for your review and feedback. Much appreciated.

That key is just for the preview of the Link. The component itself doesn't have a key set internally.

However certain implementations such as the rich text implementation of Link UI does:

<LinkControl
key={ forceRemountKey }

This PR is suggesting making such an implementation the "best practice" and not trying to be clever about syncing values.

@getdave
Copy link
Contributor Author

getdave commented Jul 27, 2023

Potential bug highlighted by the failing e2e tests here.

@scruffian scruffian force-pushed the remove/value-syncing-in-link-control branch from eb6c27a to 80f8342 Compare August 2, 2023 13:39
@github-actions
Copy link

github-actions bot commented Aug 2, 2023

Flaky tests detected in 1e9288f.
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/6417847914
📝 Reported issues:

@ndiego
Copy link
Member

ndiego commented Aug 2, 2023

The update looks good (for me, personally) but also feels too technical. People who'll understand all the details there probably know about resetting the state via key.

Personally, I like the extra details. While I can comfortably build "stuff" with React, my knowledge of React fundamentals is not comprehensive, which I imagine is the case for many other developers. The added insight was helpful.

Unless @ryanwelcher feels differently, I would move forward.

@scruffian
Copy link
Contributor

The test failure is genuine. It happens when we update a link value in the button component and then that needs to get synced back to link control. I'm not sure if that means we need this code after all, or if there's another way to achieve that.

@getdave
Copy link
Contributor Author

getdave commented Aug 8, 2023

The test failure is genuine. It happens when we update a link value in the button component and then that needs to get synced back to link control. I'm not sure if that means we need this code after all, or if there's another way to achieve that.

Ok let's look into that...thanks for confirming the issue @scruffian 🙇

@getdave getdave dismissed kevin940726’s stale review August 8, 2023 09:24

The feature causes bugs so let's re-review when it's ready.

@getdave
Copy link
Contributor Author

getdave commented Aug 9, 2023

I've taken a look at the test failure and it does indeed seem legit.

npm run test:e2e:playwright -- -g "appends http protocol to links added which are missing a protocol"

The issue is that as @scruffian described. The buttons block uses prependHttp to modify the url of the new value object passed to the onChange callback by LinkControl:

onChange={ ( {
url: newURL = '',
opensInNewTab: newOpensInNewTab,
} ) => {
setAttributes( { url: prependHTTP( newURL ) } );
if ( opensInNewTab !== newOpensInNewTab ) {
onToggleOpenInNewTab( newOpensInNewTab );
}
} }

This causes the value passed as value to change and become out of sync with the internal value. This is why we have the effect to sync the internal value back up with newly provided value.

The component is a controlled component so you should be able to modify the provided value and see that reflected in the component. However, we are also maintaining some internal state (which is kinda of a hybrid approach) in order that the component can control if/when it can be submitted. Traditionally the consumer would handle that kind of logic but it would place a burden on them.

Another reason for the internal state is to handle things like checkboxes for Open in new tab which shouldn't modify the value unless the user specifically chooses to Save the changes in the UI. Previously we had a lot of bugs whereby users would toggle Opens in new tab and find their changes applied unexpectedly.

The only way I can see this working is if we remove the internal state and make the component fully controlled. This would implement IoC but have the consequence of placing the burden of predictable behaviour onto the consumer. For example, they would need to implement all the logic to determine if/when a value is valid and can be submitted.

One way around this might be to expose some standardised methods on the component (LinkControl.isSubmittable & LinkControl.isValid ) which consumers could call in their onChange callback to determine whether the controlled value should be updated. This would be a breaking change however, because the component would start to behave differently from this point forward requiring all 3rd party consumers to update their implementations.

The benefits of such a change would be we remove this hybrid modal and get a predictable controlled component. The downsides are breaking backwards compatibility and also placing additional burden on the consumer.

Does anyone have any better suggestions? Which route should we take?

@Mamaduka
Copy link
Member

Mamaduka commented Aug 9, 2023

Thank you, @getdave!

Unfortunately, I don't have any bright ideas at the moment. The explorations here should show us the pain points of the component better, which would be helpful in the future.

Let's keep this PR or issue open for now, and I'll try to experiment with "value syncing" removal more when I get a chance.

P.S. I think prepending the HTTP logic could be absorbed by the LinkControl component. I thought it was already doing that, but it looks like Link format also handles it separately like the Button.

@getdave
Copy link
Contributor Author

getdave commented Aug 9, 2023

I think prepending the HTTP logic could be absorbed by the LinkControl component. I thought it was already doing that, but it looks like Link format also handles it separately like the Button.

Yes it seems like this is key functionality which could be absorbed. As long as you and I are careful that it's not seen as a solution to allowing this PR to merge though 😓

@Mamaduka Mamaduka added the [Status] Blocked Used to indicate that a current effort isn't able to move forward label Aug 9, 2023
ajlende
ajlende previously requested changes Aug 9, 2023
Copy link
Contributor

@ajlende ajlende left a comment

Choose a reason for hiding this comment

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

Leaving a blocking review so it isn't merged before the HTTP logic is absorbed by the LinkControl component.

@getdave
Copy link
Contributor Author

getdave commented Aug 30, 2023

@Mamaduka I've been thinking about this. I think one option could be to get rid of this internal value code and instead make this a true controlled input.

In practice this would mean the following API:

  • onChange - called on any modification to the value via the control. This includes typing in the inputs or toggling controls. Does not signal that the user has committed their changes.
  • onSubmit - called when the user actively commits their changes.

So consumers would wire things up as follows:

const value = {
    // existing link value from persistence layer somewhere.
    // for example block attributes
};

const [newLink, setNewLink] = useState(value);

function persistLink(val) {
    // function which updates the persistence layer with
    // the new link.
}


return (
    <LinkControl
        value={newLink}
        onChange={setNewLink}
        onSubmit={persistLink}
    />
)

The benefit is that whilst the consumer accepts responsibility for wiring up the temporary state of the link value (via onChange) they can be sure that the component will only call onSubmit when the user wants to commit changes. That means they don't have to worry about anything to do with the UI/UX in the data handling logic. All they need do is fulfill the contract of onChange and onSubmit.

The problem I see with the approach is backwards compatibility. Up until now onChange has implied the value is being committed as we handle the temporary changes internally within the component (thus the value syncing).

We have two options:

  • We change the component API and accept potential for breaking change - this could be said to be legitimate because the component is __experimental. However in practice it has been so widely used that this would probably not be a good approach.
  • We make onChange effectively onSubmit and add a new property onTempChange to handle the temporary values. This would need to be well tested!

Questions:

  • what do we gain from removing internal value syncing and making the component controlled?
  • is backwards compatibility possible?
  • is this approach worth the effort?

@Mamaduka
Copy link
Member

Thank you, @getdave!

As you mentioned in your previous comment, that burdens consumers more. While I don't think it's an issue if we have used this method from the start, now it might be perceived as a significant breaking change. We have to remember that consumers have to support multiple versions of WP.

Let's make this a low priority for now; the gain probably isn't worth it, and the annoying issues could be solved by memoizing the value passed to a component (#53507).

What do you think?

@getdave
Copy link
Contributor Author

getdave commented Aug 30, 2023

...burdens consumers more. While I don't think it's an issue if we have used this method from the start, now it might be perceived as a significant breaking change. We have to remember that consumers have to support multiple versions of WP.

Exactly. This is the major problem. I think the advantages aren't worth it, especially with the backwards compatibility concerns I outlined before.

Let's make this a low priority for now; the gain probably isn't worth it, and the annoying issues could be solved by memoizing the value passed to a component (#53507).

👍 I think we should update the documentation for the component to recommend this approach.

@kevin940726
Copy link
Member

Sorry for coming back to this so late! It seems like changing the API dramatically is not worth it at this stage for backward-compatibility reasons. Using key isn't always the best way either as it'll sometimes unmount the component and cause the link to lose focus, and it's a rabbit hole to fix it all at this stage. It feels to me that we want to keep the internal syncing, but maybe we can find a quick way to avoid syncing with useEffect, which causes double-render.

I propose deriving states by storing states from the previous renders instead:

- useEffect( () => {
- 	setInternalValue( ( prevValue ) => {
- 		if ( value && value !== prevValue ) {
- 			return value;
- 		}
- 
- 		return prevValue;
- 	} );
- }, [ value ] );
+ const [ previousValue, setPreviousValue ] = useState( value );
+ 
+ if ( value !== previousValue ) {
+ 	setPreviousValue( value );
+ 	if ( value !== internalValue ) {
+ 		setInternalValue( value );
+ 	}
+ }

It seems to work in my testing, WDYT?

@getdave
Copy link
Contributor Author

getdave commented Sep 21, 2023

It seems to work in my testing, WDYT?

I've tested this and it appears to work as described 🥳

The unit test below will pass as well:

npm run test:unit packages/block-editor/src/components/link-control/ -- --t="should reset state upon controlled value change"

Copy link
Member

@kevin940726 kevin940726 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! Looks good! The e2e failure doesn't seem related, that test has been quite flaky for a while.


Why would this happen?

React's reconciliation algorithm means that if you return the same element from a component, it keeps the nodes around as an optimization, even if the props change. This means that if you render two (or more) `<LinkControl>`s, it is possible that the `value` from one will appear in the other as you move between them.
Copy link
Member

Choose a reason for hiding this comment

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

This could be slightly misleading. React only does that when the component node is at the same place in the tree. This happens in gutenberg because we use SlotFill to portal the component to different places, which in the meantime tricks the React reconciler into thinking that they are the same node.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Questions:

  • How would you recommend we improve this section of the README?
  • Shall we just remove this stuff about key? Or do you think it remains relevant (I think it is)?

Copy link
Member

Choose a reason for hiding this comment

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

I don't have a preference TBH. I think it's okay to keep this, but maybe we can also make it less misleading. We could also add a link to the official React doc about reconciling for reference.

@getdave getdave dismissed ajlende’s stale review September 21, 2023 10:31

No longer blocked

@Mamaduka
Copy link
Member

I'm not sure about the new solution. It doesn't do much if the component doesn't receive a stable value reference; when the value changes, both states will be updated. Basically, we're just replacing effect with state update during render.

Yes, it fixes double re-rendering, but not the original issue.

@getdave
Copy link
Contributor Author

getdave commented Sep 21, 2023

Pinging @Mamaduka for a final 👀 before we merge this one.

I suggest that if merged we do not include this in WP 6.4. That will allow sufficient time for any unintended consequences of the changes to filter through outside of a major release BETA period.

It can then safely land in 6.5.

@kevin940726
Copy link
Member

I'm not sure about the new solution. It doesn't do much if the component doesn't receive a stable value reference; when the value changes, both states will be updated. Basically, we're just replacing effect with state update during render.

Yep! It's simply a replacement for the useEffect. I don't think it's ideal, but still better than the useEffect IMHO.

Yes, it fixes double re-rendering, but not the original issue.

Is the original issue about value being an object rather than a stable primitive value? I'm not sure if we can solve it without introducing breaking change, if I'm not mistaken. I think it's okay to be a follow-up if needed. This PR is already a great improvement, and maybe a new thread exploring the potential ideas could freshen up our minds 😉! That is, we can merge this without closing #51256. And perhaps update the original issue description to clarify things too?

@Mamaduka
Copy link
Member

Mamaduka commented Sep 22, 2023

I guess we can also do a deep equality checks and only sync state then. WDYT?

But I'm also okay with current solution.

Example:

if ( ! fastDeepEqual( value, internalValue ) ) {
    setInternalValue( value );
}

@kevin940726
Copy link
Member

I guess we can also do a deep equality checks and only sync state then. WDYT?

Sounds good to me too! 💯

@Mamaduka
Copy link
Member

Okay, I've adjusted a snippet a little bit. We want to keep the previous value snapshot in a separate state and compare it against it as the internal state changes based on user input.

if ( ! fastDeepEqual( value, previousValue ) ) {
    setPreviousValue( value );
    setInternalValue( value );
}

@getdave getdave force-pushed the remove/value-syncing-in-link-control branch from cdc8734 to 1e9288f Compare October 5, 2023 10:30
@getdave
Copy link
Contributor Author

getdave commented Oct 5, 2023

Okay, I've adjusted a snippet a little bit. We want to keep the previous value snapshot in a separate state and compare it against it as the internal state changes based on user input.

if ( ! fastDeepEqual( value, previousValue ) ) {
    setPreviousValue( value );
    setInternalValue( value );
}

I've applied this change and tests are ✅

Specifically this test

npm run test:unit packages/block-editor/src/components/link-control -- -t="should reset state upon controlled value change"

@Mamaduka
Copy link
Member

Mamaduka commented Oct 5, 2023

Thanks, @getdave! Let's see if all tests are passing, including e2e, and then I think we're good to merge.

@getdave getdave merged commit 0daa67b into trunk Oct 9, 2023
51 checks passed
@getdave getdave deleted the remove/value-syncing-in-link-control branch October 9, 2023 09:08
@getdave getdave removed the [Status] Blocked Used to indicate that a current effort isn't able to move forward label Oct 9, 2023
@github-actions github-actions bot added this to the Gutenberg 16.9 milestone Oct 9, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Link Editing Link components (LinkControl, URLInput) and integrations (RichText link formatting) [Type] Code Quality Issues or PRs that relate to code quality
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Remove value sync hook in Link Control
6 participants