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

compose: Add types to createHigherOrderComponent, ifCondition and pure #30881

Merged
merged 4 commits into from
Apr 29, 2021

Conversation

sarayourfriend
Copy link
Contributor

Description

Adds types to createHigherOrderComponent and ifCondition. I converted both to TypeScript as it simplified the typing process greatly for these highly abstract functions.

Part of #18838

How has this been tested?

Type checks pass. No runtime changes.

Types of changes

Enhancement

Checklist:

  • My code is tested.
  • My code follows the WordPress code style.
  • [N/A] My code follows the accessibility standards.
  • [N/A] I've tested my changes with keyboard and screen readers.
  • My code has proper inline documentation.
  • I've included developer documentation if appropriate.
  • I've updated all React Native files affected by any refactorings/renamings in this PR (please manually search all *.native.js files for terms that need renaming or removal).

@sarayourfriend sarayourfriend added [Type] Code Quality Issues or PRs that relate to code quality npm Packages Related to npm packages [Package] Compose /packages/compose labels Apr 15, 2021
@sarayourfriend sarayourfriend requested review from gziolo and tyxla April 15, 2021 21:08
@sarayourfriend sarayourfriend self-assigned this Apr 15, 2021
@sarayourfriend sarayourfriend changed the title compose: Add types to createHigherOrderComponent and ifCondition compose: Add types to createHigherOrderComponent and ifCondition Apr 15, 2021
@github-actions
Copy link

github-actions bot commented Apr 15, 2021

Size Change: -75.2 kB (-5%) ✅

Total Size: 1.39 MB

Filename Size Change
build/a11y/index.js 1.12 kB -24 B (-2%)
build/annotations/index.js 2.93 kB -849 B (-22%) 🎉
build/api-fetch/index.js 2.42 kB -993 B (-29%) 🎉
build/autop/index.js 2.28 kB -551 B (-19%) 👏
build/blob/index.js 672 B +8 B (+1%)
build/block-directory/index.js 6.59 kB -2.03 kB (-24%) 🎉
build/block-directory/style-rtl.css 993 B -12 B (-1%)
build/block-directory/style.css 995 B -11 B (-1%)
build/block-editor/index.js 116 kB -13.9 kB (-11%) 👏
build/block-editor/style-rtl.css 13 kB +432 B (+3%)
build/block-editor/style.css 13 kB +429 B (+3%)
build/block-library/blocks/button/style-rtl.css 501 B -2 B (0%)
build/block-library/blocks/button/style.css 500 B -3 B (-1%)
build/block-library/blocks/columns/style-rtl.css 422 B -14 B (-3%)
build/block-library/blocks/columns/style.css 422 B -13 B (-3%)
build/block-library/blocks/cover/editor-rtl.css 603 B -2 B (0%)
build/block-library/blocks/cover/editor.css 604 B -1 B (0%)
build/block-library/blocks/cover/style-rtl.css 1.22 kB -8 B (-1%)
build/block-library/blocks/cover/style.css 1.22 kB -7 B (-1%)
build/block-library/blocks/file/frontend.js 773 B +8 B (+1%)
build/block-library/blocks/freeform/editor-rtl.css 2.45 kB +4 B (0%)
build/block-library/blocks/freeform/editor.css 2.45 kB +4 B (0%)
build/block-library/blocks/gallery/style-rtl.css 1.06 kB -36 B (-3%)
build/block-library/blocks/gallery/style.css 1.05 kB -35 B (-3%)
build/block-library/blocks/legacy-widget/editor-rtl.css 557 B +159 B (+40%) 🚨
build/block-library/blocks/legacy-widget/editor.css 557 B +158 B (+40%) 🚨
build/block-library/blocks/media-text/editor-rtl.css 176 B -15 B (-8%)
build/block-library/blocks/media-text/editor.css 176 B -15 B (-8%)
build/block-library/blocks/media-text/style-rtl.css 492 B -43 B (-8%)
build/block-library/blocks/media-text/style.css 489 B -43 B (-8%)
build/block-library/blocks/navigation-link/style-rtl.css 1.17 kB +93 B (+9%) 🔍
build/block-library/blocks/navigation-link/style.css 1.17 kB +99 B (+9%) 🔍
build/block-library/blocks/navigation/editor-rtl.css 1.19 kB -52 B (-4%)
build/block-library/blocks/navigation/editor.css 1.19 kB -53 B (-4%)
build/block-library/blocks/query/editor-rtl.css 131 B -679 B (-84%) 🏆
build/block-library/blocks/query/editor.css 132 B -677 B (-84%) 🏆
build/block-library/blocks/table/style-rtl.css 407 B +5 B (+1%)
build/block-library/blocks/table/style.css 407 B +5 B (+1%)
build/block-library/blocks/template-part/editor-rtl.css 551 B -1 B (0%)
build/block-library/blocks/template-part/editor.css 550 B -1 B (0%)
build/block-library/blocks/video/editor-rtl.css 569 B +1 B (0%)
build/block-library/blocks/video/editor.css 570 B +1 B (0%)
build/block-library/blocks/video/style-rtl.css 169 B -4 B (-2%)
build/block-library/blocks/video/style.css 169 B -4 B (-2%)
build/block-library/editor-rtl.css 9.53 kB -304 B (-3%)
build/block-library/editor.css 9.52 kB -301 B (-3%)
build/block-library/index.js 142 kB -12.2 kB (-8%)
build/block-library/reset-rtl.css 506 B +4 B (+1%)
build/block-library/reset.css 507 B +4 B (+1%)
build/block-library/style-rtl.css 9.46 kB +24 B (0%)
build/block-library/style.css 9.46 kB +22 B (0%)
build/block-serialization-default-parser/index.js 1.3 kB -576 B (-31%) 🎉
build/blocks/index.js 46.6 kB -1.95 kB (-4%)
build/components/index.js 270 kB -14.7 kB (-5%)
build/components/style-rtl.css 16.3 kB +35 B (0%)
build/components/style.css 16.3 kB +33 B (0%)
build/compose/index.js 9.96 kB -1.61 kB (-14%) 👏
build/core-data/index.js 12 kB -5.19 kB (-30%) 🎉
build/customize-widgets/index.js 5.79 kB -2.46 kB (-30%) 🎉
build/customize-widgets/style-rtl.css 666 B +36 B (+6%) 🔍
build/customize-widgets/style.css 667 B +36 B (+6%) 🔍
build/data-controls/index.js 828 B -8 B (-1%)
build/data/index.js 7.55 kB -1.32 kB (-15%) 👏
build/date/index.js 31.8 kB -42 B (0%)
build/deprecated/index.js 738 B -49 B (-6%)
build/dom/index.js 4.61 kB -515 B (-10%) 👏
build/edit-navigation/index.js 13.3 kB -3.71 kB (-22%) 🎉
build/edit-navigation/style-rtl.css 2.84 kB -16 B (-1%)
build/edit-navigation/style.css 2.85 kB -16 B (-1%)
build/edit-post/index.js 335 kB -4.62 kB (-1%)
build/edit-post/style-rtl.css 6.94 kB -18 B (0%)
build/edit-post/style.css 6.93 kB -16 B (0%)
build/edit-site/index.js 25.2 kB -3.51 kB (-12%) 👏
build/edit-site/style-rtl.css 4.9 kB +1 B (0%)
build/edit-site/style.css 4.89 kB +2 B (0%)
build/edit-widgets/index.js 13 kB -3.69 kB (-22%) 🎉
build/edit-widgets/style-rtl.css 2.98 kB +6 B (0%)
build/edit-widgets/style.css 2.98 kB +6 B (0%)
build/editor/index.js 60.5 kB +17.8 kB (+42%) 🚨
build/element/index.js 3.44 kB -1.18 kB (-26%) 🎉
build/escape-html/index.js 739 B +4 B (+1%)
build/format-library/index.js 5.68 kB -1.09 kB (-16%) 👏
build/hooks/index.js 1.76 kB -516 B (-23%) 🎉
build/html-entities/index.js 628 B +6 B (+1%)
build/i18n/index.js 3.73 kB -310 B (-8%)
build/is-shallow-equal/index.js 709 B +10 B (+1%)
build/keyboard-shortcuts/index.js 1.65 kB -880 B (-35%) 🎉
build/keycodes/index.js 1.43 kB -515 B (-26%) 🎉
build/list-reusable-blocks/index.js 2.07 kB -1.13 kB (-35%) 🎉
build/media-utils/index.js 3.08 kB -2.31 kB (-43%) 🎉
build/notices/index.js 1.07 kB -777 B (-42%) 🎉
build/nux/index.js 2.31 kB -1.11 kB (-32%) 🎉
build/nux/style-rtl.css 718 B -13 B (-2%)
build/nux/style.css 716 B -11 B (-2%)
build/plugins/index.js 2.01 kB -945 B (-32%) 🎉
build/primitives/index.js 1.03 kB -386 B (-27%) 🎉
build/react-i18n/index.js 924 B -529 B (-36%) 🎉
build/redux-routine/index.js 2.82 kB -13 B (0%)
build/reusable-blocks/index.js 2.56 kB -1.24 kB (-33%) 🎉
build/rich-text/index.js 11.7 kB -1.85 kB (-14%) 👏
build/server-side-render/index.js 1.64 kB -961 B (-37%) 🎉
build/shortcode/index.js 1.68 kB -15 B (-1%)
build/token-list/index.js 848 B -422 B (-33%) 🎉
build/url/index.js 1.95 kB -1.06 kB (-35%) 🎉
build/viewport/index.js 1.28 kB -571 B (-31%) 🎉
build/warning/index.js 1.14 kB -1 B (0%)
build/wordcount/index.js 1.24 kB +14 B (+1%)
ℹ️ View Unchanged
Filename Size Change
build/block-library/blocks/archives/editor-rtl.css 61 B 0 B
build/block-library/blocks/archives/editor.css 60 B 0 B
build/block-library/blocks/audio/editor-rtl.css 58 B 0 B
build/block-library/blocks/audio/editor.css 58 B 0 B
build/block-library/blocks/audio/style-rtl.css 112 B 0 B
build/block-library/blocks/audio/style.css 112 B 0 B
build/block-library/blocks/block/editor-rtl.css 161 B 0 B
build/block-library/blocks/block/editor.css 161 B 0 B
build/block-library/blocks/button/editor-rtl.css 475 B 0 B
build/block-library/blocks/button/editor.css 474 B 0 B
build/block-library/blocks/buttons/editor-rtl.css 315 B 0 B
build/block-library/blocks/buttons/editor.css 315 B 0 B
build/block-library/blocks/buttons/style-rtl.css 368 B 0 B
build/block-library/blocks/buttons/style.css 368 B 0 B
build/block-library/blocks/calendar/style-rtl.css 208 B 0 B
build/block-library/blocks/calendar/style.css 208 B 0 B
build/block-library/blocks/categories/editor-rtl.css 84 B 0 B
build/block-library/blocks/categories/editor.css 83 B 0 B
build/block-library/blocks/categories/style-rtl.css 79 B 0 B
build/block-library/blocks/categories/style.css 79 B 0 B
build/block-library/blocks/code/style-rtl.css 90 B 0 B
build/block-library/blocks/code/style.css 90 B 0 B
build/block-library/blocks/columns/editor-rtl.css 190 B 0 B
build/block-library/blocks/columns/editor.css 190 B 0 B
build/block-library/blocks/embed/editor-rtl.css 486 B 0 B
build/block-library/blocks/embed/editor.css 486 B 0 B
build/block-library/blocks/embed/style-rtl.css 401 B 0 B
build/block-library/blocks/embed/style.css 400 B 0 B
build/block-library/blocks/file/editor-rtl.css 301 B 0 B
build/block-library/blocks/file/editor.css 300 B 0 B
build/block-library/blocks/file/style-rtl.css 255 B 0 B
build/block-library/blocks/file/style.css 255 B 0 B
build/block-library/blocks/gallery/editor-rtl.css 704 B 0 B
build/block-library/blocks/gallery/editor.css 705 B 0 B
build/block-library/blocks/group/editor-rtl.css 160 B 0 B
build/block-library/blocks/group/editor.css 160 B 0 B
build/block-library/blocks/group/style-rtl.css 57 B 0 B
build/block-library/blocks/group/style.css 57 B 0 B
build/block-library/blocks/heading/editor-rtl.css 129 B 0 B
build/block-library/blocks/heading/editor.css 129 B 0 B
build/block-library/blocks/heading/style-rtl.css 76 B 0 B
build/block-library/blocks/heading/style.css 76 B 0 B
build/block-library/blocks/html/editor-rtl.css 281 B 0 B
build/block-library/blocks/html/editor.css 281 B 0 B
build/block-library/blocks/image/editor-rtl.css 717 B 0 B
build/block-library/blocks/image/editor.css 716 B 0 B
build/block-library/blocks/image/style-rtl.css 476 B 0 B
build/block-library/blocks/image/style.css 478 B 0 B
build/block-library/blocks/latest-comments/style-rtl.css 281 B 0 B
build/block-library/blocks/latest-comments/style.css 282 B 0 B
build/block-library/blocks/latest-posts/editor-rtl.css 137 B 0 B
build/block-library/blocks/latest-posts/editor.css 137 B 0 B
build/block-library/blocks/latest-posts/style-rtl.css 523 B 0 B
build/block-library/blocks/latest-posts/style.css 522 B 0 B
build/block-library/blocks/list/style-rtl.css 63 B 0 B
build/block-library/blocks/list/style.css 63 B 0 B
build/block-library/blocks/more/editor-rtl.css 434 B 0 B
build/block-library/blocks/more/editor.css 434 B 0 B
build/block-library/blocks/navigation-link/editor-rtl.css 597 B 0 B
build/block-library/blocks/navigation-link/editor.css 597 B 0 B
build/block-library/blocks/navigation/style-rtl.css 272 B 0 B
build/block-library/blocks/navigation/style.css 271 B 0 B
build/block-library/blocks/nextpage/editor-rtl.css 395 B 0 B
build/block-library/blocks/nextpage/editor.css 395 B 0 B
build/block-library/blocks/page-list/editor-rtl.css 239 B 0 B
build/block-library/blocks/page-list/editor.css 240 B 0 B
build/block-library/blocks/page-list/style-rtl.css 167 B 0 B
build/block-library/blocks/page-list/style.css 167 B 0 B
build/block-library/blocks/paragraph/editor-rtl.css 157 B 0 B
build/block-library/blocks/paragraph/editor.css 157 B 0 B
build/block-library/blocks/paragraph/style-rtl.css 247 B 0 B
build/block-library/blocks/paragraph/style.css 248 B 0 B
build/block-library/blocks/post-author/editor-rtl.css 209 B 0 B
build/block-library/blocks/post-author/editor.css 209 B 0 B
build/block-library/blocks/post-author/style-rtl.css 183 B 0 B
build/block-library/blocks/post-author/style.css 184 B 0 B
build/block-library/blocks/post-comments-form/style-rtl.css 250 B 0 B
build/block-library/blocks/post-comments-form/style.css 250 B 0 B
build/block-library/blocks/post-content/editor-rtl.css 139 B 0 B
build/block-library/blocks/post-content/editor.css 139 B 0 B
build/block-library/blocks/post-excerpt/editor-rtl.css 73 B 0 B
build/block-library/blocks/post-excerpt/editor.css 73 B 0 B
build/block-library/blocks/post-excerpt/style-rtl.css 69 B 0 B
build/block-library/blocks/post-excerpt/style.css 69 B 0 B
build/block-library/blocks/post-featured-image/editor-rtl.css 338 B 0 B
build/block-library/blocks/post-featured-image/editor.css 338 B 0 B
build/block-library/blocks/post-featured-image/style-rtl.css 100 B 0 B
build/block-library/blocks/post-featured-image/style.css 100 B 0 B
build/block-library/blocks/post-title/style-rtl.css 60 B 0 B
build/block-library/blocks/post-title/style.css 60 B 0 B
build/block-library/blocks/preformatted/style-rtl.css 103 B 0 B
build/block-library/blocks/preformatted/style.css 103 B 0 B
build/block-library/blocks/pullquote/editor-rtl.css 183 B 0 B
build/block-library/blocks/pullquote/editor.css 183 B 0 B
build/block-library/blocks/pullquote/style-rtl.css 318 B 0 B
build/block-library/blocks/pullquote/style.css 318 B 0 B
build/block-library/blocks/query-loop/editor-rtl.css 83 B 0 B
build/block-library/blocks/query-loop/editor.css 82 B 0 B
build/block-library/blocks/query-loop/style-rtl.css 315 B 0 B
build/block-library/blocks/query-loop/style.css 317 B 0 B
build/block-library/blocks/query-pagination-numbers/editor-rtl.css 122 B 0 B
build/block-library/blocks/query-pagination-numbers/editor.css 121 B 0 B
build/block-library/blocks/query-pagination/editor-rtl.css 270 B 0 B
build/block-library/blocks/query-pagination/editor.css 262 B 0 B
build/block-library/blocks/query-pagination/style-rtl.css 168 B 0 B
build/block-library/blocks/query-pagination/style.css 168 B 0 B
build/block-library/blocks/query-title/editor-rtl.css 86 B 0 B
build/block-library/blocks/query-title/editor.css 86 B 0 B
build/block-library/blocks/quote/style-rtl.css 169 B 0 B
build/block-library/blocks/quote/style.css 169 B 0 B
build/block-library/blocks/rss/editor-rtl.css 201 B 0 B
build/block-library/blocks/rss/editor.css 202 B 0 B
build/block-library/blocks/rss/style-rtl.css 290 B 0 B
build/block-library/blocks/rss/style.css 290 B 0 B
build/block-library/blocks/search/editor-rtl.css 189 B 0 B
build/block-library/blocks/search/editor.css 189 B 0 B
build/block-library/blocks/search/style-rtl.css 359 B 0 B
build/block-library/blocks/search/style.css 362 B 0 B
build/block-library/blocks/separator/editor-rtl.css 99 B 0 B
build/block-library/blocks/separator/editor.css 99 B 0 B
build/block-library/blocks/separator/style-rtl.css 251 B 0 B
build/block-library/blocks/separator/style.css 251 B 0 B
build/block-library/blocks/shortcode/editor-rtl.css 512 B 0 B
build/block-library/blocks/shortcode/editor.css 512 B 0 B
build/block-library/blocks/site-logo/editor-rtl.css 440 B 0 B
build/block-library/blocks/site-logo/editor.css 441 B 0 B
build/block-library/blocks/site-logo/style-rtl.css 154 B 0 B
build/block-library/blocks/site-logo/style.css 154 B 0 B
build/block-library/blocks/social-link/editor-rtl.css 164 B 0 B
build/block-library/blocks/social-link/editor.css 165 B 0 B
build/block-library/blocks/social-links/editor-rtl.css 796 B 0 B
build/block-library/blocks/social-links/editor.css 795 B 0 B
build/block-library/blocks/social-links/style-rtl.css 1.32 kB 0 B
build/block-library/blocks/social-links/style.css 1.33 kB 0 B
build/block-library/blocks/spacer/editor-rtl.css 308 B 0 B
build/block-library/blocks/spacer/editor.css 308 B 0 B
build/block-library/blocks/spacer/style-rtl.css 48 B 0 B
build/block-library/blocks/spacer/style.css 48 B 0 B
build/block-library/blocks/table/editor-rtl.css 478 B 0 B
build/block-library/blocks/table/editor.css 478 B 0 B
build/block-library/blocks/tag-cloud/editor-rtl.css 118 B 0 B
build/block-library/blocks/tag-cloud/editor.css 118 B 0 B
build/block-library/blocks/tag-cloud/style-rtl.css 94 B 0 B
build/block-library/blocks/tag-cloud/style.css 94 B 0 B
build/block-library/blocks/term-description/editor-rtl.css 90 B 0 B
build/block-library/blocks/term-description/editor.css 90 B 0 B
build/block-library/blocks/text-columns/editor-rtl.css 95 B 0 B
build/block-library/blocks/text-columns/editor.css 95 B 0 B
build/block-library/blocks/text-columns/style-rtl.css 166 B 0 B
build/block-library/blocks/text-columns/style.css 166 B 0 B
build/block-library/blocks/verse/style-rtl.css 87 B 0 B
build/block-library/blocks/verse/style.css 87 B 0 B
build/block-library/common-rtl.css 1.31 kB 0 B
build/block-library/common.css 1.31 kB 0 B
build/block-library/theme-rtl.css 692 B 0 B
build/block-library/theme.css 693 B 0 B
build/block-serialization-spec-parser/index.js 3.06 kB 0 B
build/dom-ready/index.js 577 B 0 B
build/edit-post/classic-rtl.css 454 B 0 B
build/edit-post/classic.css 454 B 0 B
build/editor/style-rtl.css 3.9 kB 0 B
build/editor/style.css 3.9 kB 0 B
build/format-library/style-rtl.css 637 B 0 B
build/format-library/style.css 639 B 0 B
build/list-reusable-blocks/style-rtl.css 629 B 0 B
build/list-reusable-blocks/style.css 628 B 0 B
build/priority-queue/index.js 791 B 0 B
build/reusable-blocks/style-rtl.css 225 B 0 B
build/reusable-blocks/style.css 225 B 0 B

compressed-size-action

@gziolo
Copy link
Member

gziolo commented Apr 16, 2021

I converted both to TypeScript as it simplified the typing process greatly for these highly abstract functions.

Did we confirm that Gutenberg Mobile works with TS files?

@sarayourfriend
Copy link
Contributor Author

What do you mean @gziolo? It looks like the mobile React native e2e tests worked fine, do we need confirmation beyond that this is safe?

@gziolo
Copy link
Member

gziolo commented Apr 19, 2021

There is also https://github.com/wordpress-mobile/gutenberg-mobile that consumes the source code of WordPress packages. We need to ensure it understands TypeScript. As far as I can tell it doesn't use the same Babel config that Gutenberg uses:
https://github.com/wordpress-mobile/gutenberg-mobile/blob/develop/babel.config.js
https://github.com/WordPress/gutenberg/blob/trunk/packages/react-native-editor/babel.config.js

Well, we can merge this PR, but as an additional step, we will need to help the mobile team to enable TS support in https://github.com/wordpress-mobile/gutenberg-mobile if necessary.

@sarayourfriend
Copy link
Contributor Author

As long as gutenberg-mobile consumes the npm packages they should be okay, right? It's just regular JS at that point. It doesn't look like they're doing anything special with the @wordpress scope 🤔

@gziolo
Copy link
Member

gziolo commented Apr 19, 2021

As long as gutenberg-mobile consumes the npm packages they should be okay, right? It's just regular JS at that point. It doesn't look like they're doing anything special with the @wordpress scope 🤔

They don't do the regular npm consuming 😅 Every production package declares its entry point for React Native, e.g.:

"react-native": "src/index",

They use sources so we need to ensure that their setup is able to work with TypeScript files. It shouldn't be that much work as I can read at:
https://reactnative.dev/docs/typescript

@sarayourfriend
Copy link
Contributor Author

Every production package declares its entry point for React Native

🤦‍♀️ of course, I completely forgot about this.

Obviously I'm fully on board with helping them get TypeScript support anyway. What's the best way to do this, let it break and fix it later? I'm not sure how to tell how/what way it will break otherwise, but that doesn't feel good to me.

@gziolo
Copy link
Member

gziolo commented Apr 19, 2021

I asked the mobile team in their channel on WordPress Slack to help here (link requires registration at https://make.wordpress.org/chat):
https://wordpress.slack.com/archives/C02RQC4LY/p1618842203015300

@sarayourfriend sarayourfriend force-pushed the add/types-create-higher-order-component branch from 4b81b46 to a08b27b Compare April 20, 2021 22:49
@gziolo
Copy link
Member

gziolo commented Apr 21, 2021

This is what I got from @oguzkocer:

IRC metro’s default React Native Babel config has TypeScript plugin integrated and enabled for quite some time. I think it should “just work”

We can land this PR as soon as CI turns green.

@oguzkocer
Copy link
Contributor

This is what I got from @oguzkocer:

IRC metro’s default React Native Babel config has TypeScript plugin integrated and enabled for quite some time. I think it should “just work”

@gziolo Did you maybe mean to ping someone else? I can't think of a time I dealt with ^, but maybe my memory is failing me.

@gziolo
Copy link
Member

gziolo commented Apr 21, 2021

@gziolo Did you maybe mean to ping someone else? I can't think of a time I dealt with ^, but maybe my memory is failing me.

Ooops, yes. I meant @ceyhun 🙈

@ceyhun
Copy link
Member

ceyhun commented Apr 21, 2021

It would be nice to maybe have a companion Gutenberg Mobile PR to run some extra CI checks.

@sarayourfriend sarayourfriend force-pushed the add/types-create-higher-order-component branch from a08b27b to 71923a0 Compare April 21, 2021 13:24
@sarayourfriend sarayourfriend changed the title compose: Add types to createHigherOrderComponent and ifCondition compose: Add types to createHigherOrderComponent, ifCondition and pure Apr 21, 2021
@sarayourfriend
Copy link
Contributor Author

It would be nice to maybe have a companion Gutenberg Mobile PR to run the some extra CI checks.

Thanks @ceyhun, what would be the best way to do this?

@gziolo
Copy link
Member

gziolo commented Apr 21, 2021

You can open PR similar to what I had in the past: wordpress-mobile/gutenberg-mobile#3249. You only need to point the Gutenberg git submodule to the hash from this branch.

@dcalhoun
Copy link
Member

@sarayourfriend @gziolo I actually just finished opening said PR in wordpress-mobile/gutenberg-mobile#3403. We'll see what the results are. 😄

@sarayourfriend
Copy link
Contributor Author

Looks like the GB mobile checks are all green 😁

@gziolo
Copy link
Member

gziolo commented Apr 21, 2021

@dcalhoun, thank you for help 💯

>(
mapComponentToEnhancedComponent: (
OriginalComponent: ComponentType< TProps >
) => ComponentType< Subtract< TProps, TObviatedProps > >,
Copy link
Member

Choose a reason for hiding this comment

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

Fun fact: The type of mapComponentToEnhancedComponent is HigherOrderComponent<TObviatedProps>.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

While that's true I don't think we can use the type here becuase we can't pass TProps down, as the HOC type only accepts it on the call signature and we need to grab it from above. But it is a fun fact 😁

Copy link
Member

Choose a reason for hiding this comment

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

OK, I hoped for a little bit more than a fun fact but yes, now I see that HigherOrderComponent doesn't specify all the generic types we need 🙂

* @return Component class with generated display name assigned.
*/
function createHigherOrderComponent<
TObviatedProps extends object,
Copy link
Member

Choose a reason for hiding this comment

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

Could this be called TRemovedProps? I had to look up the word "obviated" 🙂

Copy link
Contributor Author

@sarayourfriend sarayourfriend Apr 23, 2021

Choose a reason for hiding this comment

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

Yup! Removed is way simpler, I just didn't think of it before 😅

Copy link
Member

Choose a reason for hiding this comment

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

One more idea: making the naming closer to the programmer's thinking, these props are TInjectedProps. The HOC injects them into the inner component. The fact that they are removed from the outer component's type is secondary.

@jsnajdr
Copy link
Member

jsnajdr commented Apr 26, 2021

The createHigherOrderComponent is in fact much more generic that whan the <TProps, TRemovedProps> specify. The mapping function can transform any React components to any other, where the props don't need to be related at all:

type HigherOrderComponent< TInnerProps, TOuterProps > = (
  Inner: ComponentType< TInnerProps >
) => ComponentType< TOuterProps >;

function createHigherOrderComponent< TInnerProps, TOuterProps >(
  mapComponent: HigherOrderComponent< TInnerProps, TOuterProps >,
  modifierName: string
): HigherOrderComponent< TInnerProps, TOuterProps > {
  return ( Inner ) => {
    const Outer = mapComponent( Inner );
    const displayName = Inner.displayName || Inner.name || 'Component';
    Outer.displayName = `${ modifierName }(${ displayName })`;
    return Outer;
  };
}

What do you think? Isn't this a better type?

I can then define, e.g., the withI18n HOC like this:

const withI18n = createHigherOrderComponent( function < TProps >(
  Inner: ComponentType< TProps & I18nContextProps >
) {
  return ( props: TProps ) => {
    const i18nProps = useI18n();
    return <Inner { ...props } { ...i18nProps } />;
  };
},
'WithI18n' );

The type annotations are quite minimal, I don't need to use any as anywhere, and types are correctly inferred.

In an example component that's wrapped with withI18n, TypeScript is able to infer that the outer component has only the name prop (from HelloProps) and not the i18n ones:
Screenshot 2021-04-26 at 14 09 44

I was unable to make with18n work with the original createHigherOrderComponent< TProps, TRemovedProps > version.

@jsnajdr
Copy link
Member

jsnajdr commented Apr 26, 2021

There are some obstacles that prevent the new types to be used by @wordpress/compose importers:

  • missing types field in package.json
  • missing record in top-level tsconfig.json
  • the package-level index.js file is still just JavaScript, not TypeScript, so there is no index.d.ts generated in build-types and import type { ... } from '@wordpress/compose' can't be resolved.

@sarayourfriend
Copy link
Contributor Author

There are some obstacles that prevent the new types to be used by @wordpress/compose importers:

Indeed, this PR only partially types compose so it won't include any of the things you mentioned in your comment until the package is fully typed.

@sarayourfriend
Copy link
Contributor Author

What do you think? Isn't this a better type?

Yes! It's much simpler, I seem to have over complicated things 🙂

@sarayourfriend
Copy link
Contributor Author

So actually there is a problem with the types you suggested. How would you fully type annotate the ifCondition function @jsnajdr? I'm not sure what to say the return type of ifCondition is without introducing further generics into ifCondition that don't seem to make sense.

@sirreal
Copy link
Member

sirreal commented Apr 27, 2021

There are some obstacles that prevent the new types to be used by @wordpress/compose importers:

Indeed, this PR only partially types compose so it won't include any of the things you mentioned in your comment until the package is fully typed.

@sarayourfriend This point is still important so that the partial typechecking is run as expected:

missing record in top-level tsconfig.json

@jsnajdr
Copy link
Member

jsnajdr commented Apr 27, 2021

@sarayourfriend I was able to type ifCondition like this:

const ifCondition = < TProps, >(
  predicate: ( props: TProps ) => boolean
): HigherOrderComponent< TProps, TProps > =>
  createHigherOrderComponent(
    ( WrappedComponent ) => ( props ) => {
      if ( ! predicate( props ) ) {
        return null;
      }

      return <WrappedComponent { ...props } />;
    },
    'ifCondition'
  );

And types for pure can be:

const pure = createHigherOrderComponent(
  < TProps, >( Wrapped: ComponentType< TProps > ) => {
    return class extends Component< TProps > {
      shouldComponentUpdate( nextProps: TProps ) {
        return ! isShallowEqual( nextProps, this.props );
      }

      render() {
        return <Wrapped { ...this.props } />;
      }
    };
  },
  'pure'
);

There are some differences between these two. The return type of ifCondition( predicate ) can be named -- it's HigherOrderComponent< TProps, TProps > -- because there is the TProps type parameter. And the createHigherOrderComponent call doesn't need any type annotations at all, because everything is inferred.

But for pure, I don't know how to name its type:

const pure: WhatTheType = createHigherOrderComponent( ... )

What should I write in place of WhatTheType to make it work? Because the right-hand side of the expression has type annotations, the type is correctly inferred:

Screenshot 2021-04-27 at 14 01 55

But can it be written in terms of HigherOrderComponent<TProps, TProps>? I don't know.

@sarayourfriend
Copy link
Contributor Author

But for pure, I don't know how to name its type:

This is a major problem for type extraction. We need to be able to either explicitly annotate the return type in TS or as a JSDoc @type tag 😞

@jsnajdr
Copy link
Member

jsnajdr commented Apr 27, 2021

With the HigherOrderComponent< TRemovedProps, Props > version, I was unable to make types for a withI18n-like component work. Here's my doodle:

type I18nProps = {
  __: ( s: string ) => string;
};

const i18nProps: I18nProps = {
  __: ( s ) => s
};

const mapI18n = < TProps, >( Inner: ComponentType< TProps & I18nProps > ) => (
  props: TProps
) => <Inner { ...props } { ...i18nProps } />;

const withI18n = createHigherOrderComponent( mapI18n, 'WithI18n' );

Here the return type of createHigherOrderComponent is not correctly inferred: it's HigherOrderComponent< object > instead of HigherOrderComponent< I18nProps >.

And I can't pass the I18nProps directly as in createHigherOrderComponent< I18nProps >. The function has two type parameters, where one would get fixed to I18nProps and the other one, TProps, would remain generic. I don't know how to cope with that.

@jsnajdr
Copy link
Member

jsnajdr commented Apr 27, 2021

This is a major problem for type extraction. We need to be able to either explicitly annotate the return type in TS

Well the type can be written as

<TProps>(Inner: ComponentType<TProps>) => ComponentType<TProps>

But can it be written in terms of the HigherOrderComponent shorthand?

@sarayourfriend
Copy link
Contributor Author

I think it's fine to write it explicitly as the inferred type shows. Alternatively we could add aliases for these types:

export type HigherOrderComponent< TInnerProps, TOuterProps > = (
	Inner: ComponentType< TInnerProps >
) => ComponentType< TOuterProps >;

export type SimpleHigherOrderComponent = < TProps >(
	Inner: ComponentType< TProps >
) => ComponentType< TProps >;

export type PropRemovingHigherOrderComponent< TRemovedProps > = < TProps >(
	Inner: ComponentType< TProps & TRemovedProps >
) => ComponentType< TProps >;

As always waves hands names are hard.

@sarayourfriend
Copy link
Contributor Author

sarayourfriend commented Apr 28, 2021

I've tried moving forward with this PR but @jsnajdr I think I've run into another snag. Please not the comment at the end of this playground link:

https://www.typescriptlang.org/play?#code/PQKhFgCgAIWhRAHgFwKYCcB2BDANtAE1QAdVMjMBjAS1QGcpZgpqBbYge3WWgCVVslHgDN0HVtADk6AUMkBuFuy48A3tErZWqXAGFsdVABpoAV2Kl0AMWro6PAL7RR4qbg4EDACwVRgwaHpcakxkAFoCajpsACNcVDDMVBQw4KToTA4wmXt0aiFUAjC2Tm4GSBKVaGQAT1JodV1xTiTQgBU61GgnFwlpWWRfSD8wRjgACWoAcy8MaC4idA1mjlbkOg1sTE3TQ2hiMWIN5A5oGK6OGIA3amw0AgA6aCsuQMQtYnjobGhxgHldNUvHcxtAQgArVBCDbUACMAA5tgcOEdoAB3ai4fCXG53LrIWYZVCFZyvAldOGI-aHY6nc77AyGAjVU7k5bKNYPMaggCSwm+vwBhA49AyHB4ONuaCBXSSJOEry2NWgdFINGE+WpKOOs22aK6RGE2FMuB4J2gAANVA4LeivPkvKD6IZQrcscq6KYYsh0IJ1tAAAzOMQSNlcaYhPBa1Ecfls4iMkkhdktMjIJ4AQW2yQ+8VBsaBUTBOq6FuIphktv+gLR9soXmFosyPHrWym+MJGYACjyVRWjZQLnHCZQVmszqZkKC6CVcMrWB5qBrRWyQmgsHg6FyYCBmJBkqUzZ1ftNZug-uhFk0OWmADzQNo8zBJdBdmkmNp-ScYN-a6AAPmgABeaAAAooAASCfF8AC5oGvVN2k6e9H2fH8aQAqAAEpgMAhDVjTDpSBQr911-VF-0UYZ90QQ9qmPABlWdUEmGYMAvK8xzTYDoBQ8iNn-cDICgtD0Dg-C1iI1A+Iw-9sNw+CuKQ4iH34gCqKgA8qlqepyP4BcbkwKZWLPDiMAku8H30jgrkKNTkjQcgNkuSEhAAnjb0gtp+KgQTIOgjBxKU5ApJkv8ADIrNQAy7Nk+SgLw4LQtU2SNMgUAIB3aAAHFqFs7YfmEUwqGQahVmgVhsAsEIpgFUcb1CFlvmzTBgSoEl6sQngtmZBdImXdBQRwbQTBkZAK0wEtAlarZB2ZTqCMa40pm0UISQxAkBXbF88WZSJVVwbAagAOS0VBt1gUEAAEE19CRKuICz2g4eAZvaggnp4KxiqEMrtge4gapTRazVOMg2rm4HOWu26tAqxcBtO7RoBR1G0fR9GGOJZlhq6Xo7QdJrtowXbCCiT4joyM6Lsy2BoCusaJsUhqW0OugNg2htid9e4yYOynce+dnpiSR4xj3KAipKv6NBkPETPYy9zOClCAtfd8H1I9C-z84SHs+uCFfPJX0E+1XRP4j8tfVnWjEgvql1odAkdQODchq7DDdPRXOJZ82X0tzXvxtiiGkgxmsDA6A1egHCErD4SINHSaeGtnj9eC0Do9E2OqIgpPVnsPmKZOs6eLVh59pLl3oAAH1r7OXweQX66kT6hnz63K-Jw7S+RkCLQAEnUcxLBsOxkCE-Ok7OvQDFQLOHcRsusMgiCcIcUDh+L3ua4cLCLTziCI+2a284cKiHE02iqkNY1TVlgQ0CNszTeCtLk6L3ZUCfexZp-5kIFQIcDgkqOOgFYRpRGLTOAGYTxsSWK-ZmXUzBMiavSZENwiACmKtQAAjqYCkKd-7RwACJnBqKCBa0Mdx7k-jwTmv9kD-x5AQOCeloo2Rqi-E2ZsE4QRCH-dqrC3Y+iBg3TAphWDnHQJfdyIFKBy2ft7Y2vsupTzCkcEwut84AHVfQWEKAbMEyhuCgX6H6SQWEHifVCmvCC3kMKRXUII5hwi2EqjEUZOuGQpEyO6GvOSwlwH8OPqgcakcs7IiOHBRxf4QmqHsfQsExD3E8W-kwlhBAs76KqqQD6wVc72JPrxXJhiCkswaNAB4NTokbCcK4rJQEXGpMHKw7o0BgCUTXhfSCDg7bCUkIw1pADJDYQ-oXHggIgHRjoHBdQCoQGeLyEZeQKShFtI8e7bxEi-FzCcCEjRkQrj-nUHUh4jS0kOFvMAY5QSsITJTvBaZ6JqAEkye4rOgIHmdICM2fIXQvAcDRM8zxmJ8DAimpcwcZDozzEwNufw0dJASEZFIoGBIixFjZAuIu9UFzbG-psQwTw+TQBqBwUwKpPhvJlOstxMKeTkLqfMScKTzTklsPMNE2wPmbJ8ulAIOkug9UIFCQ6MhoAcIMtwlRr8zZ8oAfxQCtKMRYnRFwAA1iYGIbLewEFWJIM09pMCaqBHcYsdLcVTkFeyAlqCuiaD2AqJYmKNjVjtBgLo+oNUmmZOCXYPADxQh4LS80M54ihDnNAGQMU6UstArqGFtZlS9jRAYVBQMGJeh9H6M4qAXWoGsVAIAA

@jsnajdr
Copy link
Member

jsnajdr commented Apr 29, 2021

@sarayourfriend The trouble is this definition of withInstanceId:

type InstanceIdProps = { instanceId: string | number };

const withInstanceId: PropRemovingHOC< InstanceIdProps > = createHigherOrderComponent(
  < TProps >( Wrapped: ComponentType< TProps & InstanceIdProps > ) => {
    return ( props: TProps ) => {
      const instanceId = useInstanceId( Wrapped );
      return <Wrapped { ...props } instanceId={ instanceId } />;
    };
  }
);

Now when calling withInstanceId( C ) where C has props type { foo, instanceId }, then TProps is inferred to be { foo, instanceId } and the props of Wrapped are inferred to be { foo, instanceId } & { instanceId }. This satisfies all the type constraints and the type of the returned component includes { instanceId }.

We hoped that TProps would be inferred as { foo } and type of Wrapped (which C needs to be assignable to) would be inferred as { foo, instanceId }. That would also satisfy the type constraints, while being more minimal. But TypeScript made a different choice 🤷

I was able to fix it with this implementation:

const withInstanceId = createHigherOrderComponent(
  < TProps extends InstanceIdProps >( Wrapped: ComponentType< TProps > ) => {
    return ( props: Omit< TProps, keyof InstanceIdProps > ) => {
      const instanceId = useInstanceId( WrappedComponent );
      return <Wrapped { ...props } instanceId={ instanceId } />;
    };
  }
);

It explicitly omits InstanceIdProps from the result type, instead of adding them to the input type.

React Redux connect types do a very similar Omit on the result type:

https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react-redux/index.d.ts#L128

They do a lot of additional magic with the DistributiveOmit, Matching and Shared helpers that I assume solve some edge cases, but the basic idea is the same.

@sarayourfriend
Copy link
Contributor Author

Sweet. In that case can we move forward with this PR in its current state @jsnajdr?

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.

Let's merge this and continue iterating 👍


export type SimpleHigherOrderComponent = < TProps >(
Inner: ComponentType< TProps >
) => ComponentType< TProps >;
Copy link
Member

Choose a reason for hiding this comment

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

I wish this type could have a better name (what exactly is simple about it?) or if we could avoid it.

If HigherOrderComponent defaulted its second argument:

HigherOrderComponent< TInnerProps, TOuterProps = TInnerProps >

then we could write just

HigherOrderComponent< TInnerProps >

to declare a "simple" HOC.

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'll do this in the next PR. I agree I hate the names I chose!

Copy link
Member

Choose a reason for hiding this comment

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

OK and thanks for working on this. React HOCs are a really complex TypeScript puzzle. The types for React Redux connect are almost 700 lines of hardcore TypeScript. I wish we used other patterns (render props, hooks) where types are more straighforward.

@jsnajdr
Copy link
Member

jsnajdr commented Apr 29, 2021

In that case can we move forward with this PR in its current state

Let's do it 🚀

@sarayourfriend sarayourfriend merged commit 3328432 into trunk Apr 29, 2021
@sarayourfriend sarayourfriend deleted the add/types-create-higher-order-component branch April 29, 2021 14:53
@github-actions github-actions bot added this to the Gutenberg 10.6 milestone Apr 29, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
npm Packages Related to npm packages [Package] Compose /packages/compose [Type] Code Quality Issues or PRs that relate to code quality
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants