Skip to content

Conversation

@tkajtoch
Copy link
Member

@tkajtoch tkajtoch commented Jun 2, 2023

Summary

This is the first set of changes needed to make EUI compatible with React 18. Following @cee-chen suggestion in #6772, I divided the work into more branches that are easier to review and merge into our feature branch (feature/react-18).

This PR contains:

  • Upgrade react, react-dom and its types to v18
  • Update package.json resolutions to force @types/react version to v18
  • Fix component types to follow React 18 changes, most notably add PropsWithChildren to components accepting the children property
  • Switch to using the new createRoot API in docs entrypoint

QA

  1. Pull the changes and run yarn to fetch new dependencies
  2. Run yarn build and confirm the application builds successfully
  3. Run yarn start, wait for it to load and open http://localhost:8030 to confirm the application is loading
  4. See the homepage and other component pages opening as expected

Please note that the work is not final and there are additional changes that need to be made to make the application stable and tests passing. At this stage, some pages may crash the application, portals are not working and the majority of tests are failing. This is expected and will be addressed in next PRs.

General checklist

  • Checked in Chrome, Safari, Edge, and Firefox

@tkajtoch tkajtoch added skip-changelog Use on PRs to skip changelog requirement (Don't delete - used for automation) tech debt labels Jun 2, 2023
@tkajtoch tkajtoch requested a review from a team June 2, 2023 21:54
@tkajtoch tkajtoch self-assigned this Jun 2, 2023
@tkajtoch tkajtoch mentioned this pull request Jun 2, 2023
14 tasks
Copy link
Contributor

@cee-chen cee-chen left a comment

Choose a reason for hiding this comment

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

Hey Tomasz, huge apologies for taking so long to get to this PR while you were away. We had a fairly large set of dependency changes during your PTO (Node 18 upgrade being the biggest one that would affect this work). Would you mind rebasing/merging in latest main again, as well as running yarn/yarn lint-fix? (I'm seeing some newline/whitespace changes that are a little suspicious / that I wouldn't expect to see on latest Prettier).

Also I hate to be annoying, but I think we may need to either split up the type changes into another PR or go through each more slowly/carefully. There's way more changes than I would have expected to types (I was hoping it would be just the children change, but alas) and I'm relatively concerned about it. For better or for worse, we get a decent amount of Typescript questions and feedback, particularly around our components with generics, e.g. tables, select components, etc. - I'm a little surprised to see some of these types that used to be inferred now needing to be explicitly stated.

Comment on lines 265 to 269
"@types/react": "^16.9 || ^17.0 || ^18.0",
"@types/react-dom": "^16.9 || ^17.0 || ^18.0",
"moment": "^2.13.0",
"prop-types": "^15.5.0",
"react": "^16.12 || ^17.0",
"react": "^16.12 || ^17.0 || ^18.0",
Copy link
Contributor

Choose a reason for hiding this comment

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

[not a change request, just writing this out to remind myself] Once the feature branch is ready to merge, we should make sure to test the final EUI build/package against a dummy app (CRA?) using React 16/17 to ensure it's still backwards compatible

[id: string]: ReactNode;
}

export function getItemId<T>(item: T, itemId?: ItemId<T>) {
Copy link
Contributor

Choose a reason for hiding this comment

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

The repeated extends object type changes here feel like a red flag / code smell - worst case scenario is that this will affect the typing of every single table usage in Kibana. What happened to make this change necessary?

const root = createRoot(document.getElementById('guide'));

root.render(
<StrictMode>
Copy link
Contributor

Choose a reason for hiding this comment

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

Just to confirm, do we deliberately want strict mode on our docs? Does React have a recommendation/default on this moving forward?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'd say so. StrictMode runs additional checks during runtime and lets us know about potential issues with our code. Since the docs site is heavily used as a local development preview, I find it extremely handy to have it running React in strict mode.

I think the reason it's not enabled by default in React 18 is more for compatibility purposes than to let people choose whether they actually want their application to be strictly checked when running in development environments.

Copy link
Contributor

@cee-chen cee-chen Jun 21, 2023

Choose a reason for hiding this comment

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

I think my hesitation is purely around debugging. I can definitely see us "forgetting" or not realizing this is turned on locally, and thinking we can repro a bug in dev but not prod or vice versa. Personally, I think the most helpful option for us as frontend devs would be to add a toggle to our main header that allows us to toggle strict mode on or off - this will give us a visual hint that strict mode is enabled, and allow us to disable it dynamically to repro environments for specific bugs if necessary.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not sure if there can be issues with non-strict mode when writing and testing code in strict mode. StrictMode also only affects development environments and EUI has it enabled only for the docs site. The main difference is that components are mounted twice when running in development mode to detect errors:

To help surface these issues, React 18 introduces a new development-only check to Strict Mode. This new check will automatically unmount and remount every component, whenever a component mounts for the first time, restoring the previous state on the second mount.
(source)

I've never seen anyone having an option like that but maybe there's a reason for it. What do you say we leave it for now and add something like this if we actually find a non-StrictMode error we need to fix?

Copy link
Contributor

@cee-chen cee-chen Jun 23, 2023

Choose a reason for hiding this comment

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

Gotcha. Honestly it'll be a bit of an adjustment for me, I've never really worked in strict mode before and I wouldn't have expected, for example, double console logs due to remounting.

I might play around a bit later with adding a switcher for strict mode that goes next to our babelfish switch if you don't object - but that can go into the feature branch later.

Comment on lines 68 to 71
} & Pick<EuiBreadcrumbProps, 'truncate'> &
PropsWithChildren;

export const EuiBreadcrumb: FunctionComponent<
HTMLAttributes<HTMLLIElement> & _EuiBreadcrumbProps
Copy link
Contributor

Choose a reason for hiding this comment

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

@tkajtoch We have a majority on using the non-generic syntax it looks like - can you update all instances in this PR to use the agreed-upon syntax? Or alternatively, would you be open to separating out the types update to a separate PR to make reviewing a little easier?

@tkajtoch tkajtoch force-pushed the feat/react-18-upgrade-packages-and-types branch 2 times, most recently from e9209a6 to d0de9db Compare June 22, 2023 09:33
@tkajtoch
Copy link
Member Author

@cee-chen I rebased the branch and removed most of the unrelated changes to keep reviewing as straightforward as possible. I have them stashed to include in separate PRs when this one is merged into our feature branch.

@tkajtoch tkajtoch requested a review from cee-chen June 22, 2023 09:56
@tkajtoch tkajtoch force-pushed the feat/react-18-upgrade-packages-and-types branch 2 times, most recently from 3ff72f4 to b7d23a5 Compare June 26, 2023 16:49
...childRoutes,
];

ReactDOM.render(
Copy link
Member Author

@tkajtoch tkajtoch Jun 26, 2023

Choose a reason for hiding this comment

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

Caution, ugly github diff below! It's way better to compare changes side to side - it just wraps the app in <StrictMode>.

Copy link
Contributor

Choose a reason for hiding this comment

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

Random GitHub pro tip - using the Hide whitespace setting or adding ?w=0 to the PR URL is super helpful for added wrappers like these!

? createPortal(this.props.children, this.portalNode)
: null;
return this.portalNode ? (
<>{createPortal(this.props.children, this.portalNode)}</>
Copy link
Member Author

Choose a reason for hiding this comment

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

It's wrapped in JSX fragment because createPortal returns ReactPortal which isn't a supported FunctionComponent return type

Copy link
Contributor

Choose a reason for hiding this comment

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

Interesting - so the change is purely for typing and not because it affects actual end behavior? 🤔

Just curious, why doesn't this affect these other instances in that case?

return createPortal(

return hasValidAnchor && parentNode.current
? createPortal(bar, parentNode.current)
: bar;

Or do you mean that class components no longer support this return type (since this component is a class, not function component)?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, sorry, I meant class components and render() return value!

Copy link
Contributor

Choose a reason for hiding this comment

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

Gotcha!! Thanks for clarifying!

Comment on lines +27 to +28
type EuiCodeBlockAnnotationProps = PropsWithChildren &
CommonProps & {
Copy link
Contributor

Choose a reason for hiding this comment

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

Extra optional thought - if we're already exporting our own CommonProps type, would it make sense to just change it to CommonPropsWithChildren that extends PropsWithChildren? That way we only need one extra import? 🤷

Probably not a huge deal in the long run, just throwing that thought out there

Copy link
Contributor

Choose a reason for hiding this comment

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

@tkajtoch just a quick re-ping on this question - just wanted to get your thoughts on it! Feel free to tell me it's an unnecessary abstraction 😅

Copy link
Member Author

Choose a reason for hiding this comment

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

Sorry, looks like I missed this one!

CommonProps is used in components that both accept and don't accept passing children, so we'd need to have CommonProps as it is right now and define the new type CommonPropsWithChildren = CommonProps & PropsWithChildren.

I like that it would give us one less type to extend props with. On the other hand we'd have to document to not use the default PropsWithChildren and instead use our custom type and I don't know how I feel about that. Maybe it's just better to keep things as they are. I'm not sure. We can always switch if we feel there's a value in it - let's discuss this with the team during our next meeting!

Copy link
Contributor

Choose a reason for hiding this comment

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

No worries at all! Yeah it'd be a super minor syntactical sugar, just wanted to get your quick thoughts on whether you thought it was worth it. If you don't think it is, that's totally cool!

Comment on lines 104 to 105
> &
PropsWithChildren & {
Copy link
Contributor

@cee-chen cee-chen Jun 27, 2023

Choose a reason for hiding this comment

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

Also another annoying question that you are totally free to say you'd prefer not to enforce, but I wonder if we want to be more consistent with the order in which we declare PropsWithChildren? Would it be easier to tell if a component has children at a glance if we declared it (and CommonProps) first? e.g.,

// Least to most specific
type EuiComponentProps = PropsWithChildren &
CommonProps &
ExtendedComponentProps & {
  // ... props specific to this component
}

Copy link
Member Author

Choose a reason for hiding this comment

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

I love this idea! I updated the lines I changed to reflect that but I'd love us to document it actually make it a rule, just like module imports order for example.

@cee-chen
Copy link
Contributor

Those are all my remaining comments - a lot of them are more general questions rather than change requests. This was a breeze to review, thanks for pruning it down Tomasz! ❤️‍🔥

@tkajtoch tkajtoch force-pushed the feat/react-18-upgrade-packages-and-types branch from b7d23a5 to 86c1db2 Compare June 27, 2023 07:53
@tkajtoch tkajtoch requested a review from cee-chen June 27, 2023 07:56
Copy link
Contributor

@cee-chen cee-chen 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 taking the time to answer my q's / address my comments, it's super appreciated!! I think I have one outstanding question I just wanted to get your thoughts on, but it's not a change request - feel free to merge in! 🚢 🎉

@tkajtoch tkajtoch merged commit a29b78b into elastic:feature/react-18 Jun 29, 2023
tkajtoch added a commit that referenced this pull request Jul 11, 2023
* build(deps): Bump react and react-dom to v18

* refactor(docs): update docs site initialization to use createRoot API

* build(babel): enforce classic react runtime and allow declare fields in babel config

* refactor(*): update children prop types to use React 18 PropsWithChildren

* build(*): bump react and react-dom versions

* style(EuiForm): replace React.FC with FunctionComponent usage

* style(*): reorder PropsWithChildren usage to be from least to most specific
tkajtoch added a commit that referenced this pull request Jul 24, 2023
* build(deps): Bump react and react-dom to v18

* refactor(docs): update docs site initialization to use createRoot API

* build(babel): enforce classic react runtime and allow declare fields in babel config

* refactor(*): update children prop types to use React 18 PropsWithChildren

* build(*): bump react and react-dom versions

* style(EuiForm): replace React.FC with FunctionComponent usage

* style(*): reorder PropsWithChildren usage to be from least to most specific
tkajtoch added a commit that referenced this pull request Jul 26, 2023
* build(deps): Bump react and react-dom to v18

* refactor(docs): update docs site initialization to use createRoot API

* build(babel): enforce classic react runtime and allow declare fields in babel config

* refactor(*): update children prop types to use React 18 PropsWithChildren

* build(*): bump react and react-dom versions

* style(EuiForm): replace React.FC with FunctionComponent usage

* style(*): reorder PropsWithChildren usage to be from least to most specific
tkajtoch added a commit that referenced this pull request Jul 28, 2023
* build(deps): Bump react and react-dom to v18

* refactor(docs): update docs site initialization to use createRoot API

* build(babel): enforce classic react runtime and allow declare fields in babel config

* refactor(*): update children prop types to use React 18 PropsWithChildren

* build(*): bump react and react-dom versions

* style(EuiForm): replace React.FC with FunctionComponent usage

* style(*): reorder PropsWithChildren usage to be from least to most specific
tkajtoch added a commit that referenced this pull request Jul 31, 2023
* build(deps): Bump react and react-dom to v18

* refactor(docs): update docs site initialization to use createRoot API

* build(babel): enforce classic react runtime and allow declare fields in babel config

* refactor(*): update children prop types to use React 18 PropsWithChildren

* build(*): bump react and react-dom versions

* style(EuiForm): replace React.FC with FunctionComponent usage

* style(*): reorder PropsWithChildren usage to be from least to most specific
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-changelog Use on PRs to skip changelog requirement (Don't delete - used for automation) tech debt

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants