Skip to content

feat(react-components): add react 18 support #34456

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

Open
wants to merge 31 commits into
base: master
Choose a base branch
from

Conversation

dmytrokirpa
Copy link
Contributor

@dmytrokirpa dmytrokirpa commented May 15, 2025

Summary

This PR addresses type incompatibilities introduced by React 18 that break our Slots API. Specifically, it fixes issues with ReactNode, RefAttributes, and several slot-related utility types. It also adjusts internal type declarations to better align with actual usage and React’s updated typings.


Previous Behavior

1. ReactNode Breaking Change

React 18 introduced stricter definitions for ReactNode, removing the {} catch-all in ReactFragment. This change breaks our slot typings, which previously relied on the broader compatibility of React 17:

React 17:

type ReactFragment = {} | ReactNodeArray;
type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;

React 18:

type ReactNode =
  | ReactElement
  | string
  | number
  | Iterable<ReactNode>
  | ReactPortal
  | boolean
  | null
  | undefined
  | ExperimentalReactNodes[keyof ExperimentalReactNodes];

🔗 Playground showing the issue


2. RefAttributes Change

In React 18, RefAttributes now uses LegacyRef<T> which includes support for string refs — something we don't support in our API.

React 17:

interface RefAttributes<T> {
  ref?: Ref<T>
}

React 18:

interface RefAttributes<T> {
  ref?: LegacyRef<T>
}

3. FC doesn't provides type for children prop, it should be explicitly defined

React 17:

const Component: React.FC = ({ children }) => <>{children}</>

React 18:

// `children` is not defined in React.FC
const Component: React.FC = ({ children }) => <>{children}</>

const Component: React.FC<{ children?: React.ReactNode }> = ({ children }) => <>{children}</>

New Behavior

  1. Loosened type constraints in UnknownSlotProps
    Previously, the children prop has type ReactNode | undefined, but this doesn’t work with the SlotRenderFunction and React 18. This change removes that restriction and to enable usage with React 18.

  2. Updated SlotShorthandValue to accept Iterable<ReactNode>
    React 18 expands ReactNode to include Iterable<ReactNode>, so this change ensures our shorthand slot values also support this structure. This enables support for JSX fragments and iterable rendering.

  3. WithSlotRenderFunction adjusted for manual type assertions in React 18
    In React 18, implicit inference of slot render functions may not work correctly due to stricter type definitions. Consumers must now use as or satisfies type assertions when passing a function to slot props.

  4. Removed deprecated VoidFunctionComponent
    VoidFunctionComponent is deprecated and unnecessary since ComponentType handles the same use cases. Removing it simplifies our generic types and avoids confusion.

  5. cloneTriggerTree updated for broader children support
    This utility function now assumes children can be more than just ReactNode. It aligns with the loosened constraints and supports broader use cases, including render functions or iterables.

  6. Removed usage of React.RefAttributes in several utilities
    With React 18, RefAttributes includes LegacyRef, which supports string refs — something we don’t allow. We’ve replaced it with more precise types to enforce our internal API constraints.

  7. Added { children?: React.ReactNode } to fix story/test compatibility
    Some stories and test utilities failed under React 18 due to missing children prop typing. So the fix is too use the React.PropsWithChildren<Props> or explicitly declare the children prop type.


React 18 type changes:

Slot render functions must now be manually typed in React 18:

// React 17 – works
<Button icon={{ children: () => 'Icon' }}>Label</Button>

// React 18 – breaks, Parameter 'children' implicitly has an 'any' type.
<Button icon={{ children: () => 'Icon' }}>Label</Button>

// React 18 – fixed by manual type assertion
<Button icon={{ children: (() => 'Icon') satisfies SlotRenderFunction<IconProps> }}>
  Label
</Button>

Future Plans

Migrate slot render function away from children to a dedicated prop to improve type inference.


Related Issues

@github-actions github-actions bot added the CI label May 15, 2025
Copy link

github-actions bot commented May 15, 2025

📊 Bundle size report

Package & Exports Baseline (minified/GZIP) PR Change
react-accordion
Accordion (including children components)
108.02 kB
33.15 kB
108.066 kB
33.169 kB
46 B
19 B
react-avatar
Avatar
49.376 kB
15.835 kB
49.424 kB
15.845 kB
48 B
10 B
react-avatar
AvatarGroup
20.177 kB
7.974 kB
20.224 kB
7.99 kB
47 B
16 B
react-avatar
AvatarGroupItem
63.52 kB
20.047 kB
63.568 kB
20.059 kB
48 B
12 B
react-badge
Badge
26.002 kB
8.603 kB
26.049 kB
8.623 kB
47 B
20 B
react-badge
CounterBadge
26.781 kB
8.882 kB
26.829 kB
8.899 kB
48 B
17 B
react-badge
PresenceBadge
25.789 kB
9.472 kB
25.835 kB
9.488 kB
46 B
16 B
react-breadcrumb
@fluentui/react-breadcrumb - package
115.069 kB
31.86 kB
115.118 kB
31.877 kB
49 B
17 B
react-button
Button
37.648 kB
10.867 kB
37.694 kB
10.883 kB
46 B
16 B
react-button
CompoundButton
44.059 kB
12.168 kB
44.106 kB
12.184 kB
47 B
16 B
react-button
MenuButton
42.457 kB
12.216 kB
42.506 kB
12.233 kB
49 B
17 B
react-button
SplitButton
50.527 kB
13.782 kB
50.573 kB
13.801 kB
46 B
19 B
react-button
ToggleButton
53.578 kB
12.642 kB
53.625 kB
12.655 kB
47 B
13 B
react-card
Card - All
102.52 kB
28.908 kB
102.566 kB
28.937 kB
46 B
29 B
react-card
Card
95.162 kB
27.094 kB
95.212 kB
27.103 kB
50 B
9 B
react-card
CardFooter
14.605 kB
5.885 kB
14.651 kB
5.9 kB
46 B
15 B
react-card
CardHeader
17.138 kB
6.751 kB
17.184 kB
6.764 kB
46 B
13 B
react-card
CardPreview
14.671 kB
6.009 kB
14.718 kB
6.026 kB
47 B
17 B
react-checkbox
Checkbox
35.189 kB
12.093 kB
35.241 kB
12.109 kB
52 B
16 B
react-color-picker
ColorArea
50.26 kB
17.664 kB
50.309 kB
17.678 kB
49 B
14 B
react-color-picker
ColorPicker
18.893 kB
7.495 kB
18.939 kB
7.509 kB
46 B
14 B
react-color-picker
ColorSlider
42.436 kB
15.715 kB
42.482 kB
15.728 kB
46 B
13 B
react-combobox
Combobox (including child components)
107.617 kB
35.006 kB
107.663 kB
35.031 kB
46 B
25 B
react-combobox
Dropdown (including child components)
108.241 kB
34.952 kB
108.293 kB
34.976 kB
52 B
24 B
react-components
react-components: Button, FluentProvider & webLightTheme
69.732 kB
20.259 kB
69.779 kB
20.273 kB
47 B
14 B
react-components
react-components: Accordion, Button, FluentProvider, Image, Menu, Popover
227.58 kB
65.885 kB
227.642 kB
65.911 kB
62 B
26 B
react-components
react-components: FluentProvider & webLightTheme
44.567 kB
14.62 kB
44.613 kB
14.641 kB
46 B
21 B
react-components
react-components: entire library
1.231 MB
310.117 kB
1.231 MB
310.185 kB
87 B
68 B
react-datepicker-compat
DatePicker Compat
227.319 kB
64.364 kB
227.365 kB
64.386 kB
46 B
22 B
react-dialog
Dialog (including children components)
102.547 kB
30.738 kB
102.593 kB
30.76 kB
46 B
22 B
react-divider
Divider
21.399 kB
7.964 kB
21.445 kB
7.981 kB
46 B
17 B
react-field
Field
23.515 kB
8.917 kB
23.561 kB
8.937 kB
46 B
20 B
react-image
Image
15.432 kB
6.251 kB
15.478 kB
6.266 kB
46 B
15 B
react-input
Input
28.085 kB
9.456 kB
28.137 kB
9.476 kB
52 B
20 B
react-label
Label
14.742 kB
6.007 kB
14.788 kB
6.023 kB
46 B
16 B
react-link
Link
17.671 kB
7.155 kB
17.717 kB
7.167 kB
46 B
12 B
react-list
List
89.684 kB
26.704 kB
89.731 kB
26.718 kB
47 B
14 B
react-list
ListItem
113.027 kB
33.481 kB
113.073 kB
33.501 kB
46 B
20 B
react-menu
Menu (including children components)
157.228 kB
47.266 kB
157.289 kB
47.31 kB
61 B
44 B
react-menu
Menu (including selectable components)
160.21 kB
47.87 kB
160.271 kB
47.909 kB
61 B
39 B
react-message-bar
MessageBar (all components)
24.948 kB
9.289 kB
24.994 kB
9.303 kB
46 B
14 B
react-persona
Persona
56.267 kB
17.712 kB
56.315 kB
17.724 kB
48 B
12 B
react-popover
Popover
132.988 kB
41.417 kB
133.031 kB
41.442 kB
43 B
25 B
react-progress
ProgressBar
17.156 kB
6.9 kB
17.202 kB
6.919 kB
46 B
19 B
react-provider
FluentProvider
24.743 kB
8.92 kB
24.789 kB
8.94 kB
46 B
20 B
react-radio
Radio
32.743 kB
10.357 kB
32.789 kB
10.375 kB
46 B
18 B
react-radio
RadioGroup
15.831 kB
6.446 kB
15.878 kB
6.462 kB
47 B
16 B
react-select
Select
27.804 kB
10.14 kB
27.856 kB
10.16 kB
52 B
20 B
react-slider
Slider
38.3 kB
12.836 kB
38.346 kB
12.853 kB
46 B
17 B
react-spinbutton
SpinButton
35.284 kB
11.765 kB
35.337 kB
11.785 kB
53 B
20 B
react-spinner
Spinner
25.317 kB
8.553 kB
25.363 kB
8.57 kB
46 B
17 B
react-swatch-picker
@fluentui/react-swatch-picker - package
106.899 kB
30.866 kB
106.945 kB
30.882 kB
46 B
16 B
react-switch
Switch
35.371 kB
11.336 kB
35.418 kB
11.352 kB
47 B
16 B
react-table
DataGrid
161.429 kB
45.738 kB
161.481 kB
45.756 kB
52 B
18 B
react-table
Table (Primitives only)
42.779 kB
13.878 kB
42.838 kB
13.905 kB
59 B
27 B
react-table
Table as DataGrid
132.277 kB
36.668 kB
132.335 kB
36.693 kB
58 B
25 B
react-table
Table (Selection only)
70.647 kB
20.016 kB
70.706 kB
20.037 kB
59 B
21 B
react-table
Table (Sort only)
69.29 kB
19.629 kB
69.349 kB
19.65 kB
59 B
21 B
react-tag-picker
@fluentui/react-tag-picker - package
188.73 kB
56.597 kB
188.782 kB
56.623 kB
52 B
26 B
react-tags
InteractionTag
15.506 kB
6.232 kB
15.552 kB
6.249 kB
46 B
17 B
react-tags
Tag
30.306 kB
9.905 kB
30.353 kB
9.925 kB
47 B
20 B
react-tags
TagGroup
84.031 kB
24.92 kB
84.078 kB
24.939 kB
47 B
19 B
react-text
Text - Default
17.133 kB
6.737 kB
17.179 kB
6.753 kB
46 B
16 B
react-text
Text - Wrappers
20.293 kB
7.063 kB
20.339 kB
7.078 kB
46 B
15 B
react-textarea
Textarea
26.652 kB
9.764 kB
26.704 kB
9.784 kB
52 B
20 B
react-timepicker-compat
TimePicker
110.6 kB
36.56 kB
110.646 kB
36.578 kB
46 B
18 B
react-toast
Toast (including Toaster)
103.062 kB
30.965 kB
103.119 kB
30.976 kB
57 B
11 B
react-tooltip
Tooltip
58.64 kB
20.312 kB
58.686 kB
20.336 kB
46 B
24 B
react-tree
FlatTree
148.807 kB
42.694 kB
148.855 kB
42.71 kB
48 B
16 B
react-tree
PersonaFlatTree
149.559 kB
42.802 kB
149.607 kB
42.818 kB
48 B
16 B
react-tree
PersonaTree
145.818 kB
41.691 kB
145.865 kB
41.699 kB
47 B
8 B
react-tree
Tree
145.072 kB
41.565 kB
145.119 kB
41.574 kB
47 B
9 B
Unchanged fixtures
Package & Exports Size (minified/GZIP)
global-context
createContext
510 B
328 B
global-context
createContextSelector
537 B
339 B
react-aria
ARIA - Default
237 B
181 B
react-calendar-compat
Calendar Compat
149.672 kB
39.914 kB
react-jsx-runtime
Classic Pragma
1.101 kB
550 B
react-jsx-runtime
JSX Dev Runtime
3.815 kB
1.659 kB
react-jsx-runtime
JSX Runtime
4.411 kB
1.901 kB
react-motion
@fluentui/react-motion - createMotionComponent()
3.995 kB
1.755 kB
react-motion
@fluentui/react-motion - createPresenceComponent()
4.858 kB
2.128 kB
react-motion
@fluentui/react-motion - PresenceGroup
1.727 kB
823 B
react-overflow
hooks only
12.832 kB
4.828 kB
react-portal
Portal
15.259 kB
5.319 kB
react-portal-compat
PortalCompatProvider
8.386 kB
2.624 kB
react-positioning
usePositioning
28.865 kB
10.146 kB
react-teaching-popover
TeachingPopover
102.747 kB
30.737 kB
react-utilities
SSRProvider
180 B
160 B
🤖 This report was generated against 5ecce1e266c8bcb4df78981e973e891985e2ebe4

Copy link

Pull request demo site: URL

@dmytrokirpa dmytrokirpa self-assigned this May 16, 2025
@github-actions github-actions bot removed the CI label May 16, 2025
@dmytrokirpa dmytrokirpa changed the title React components/react 18 chore: slot api support for react 17/18 May 26, 2025
import { Caption1, ComponentProps, Slot } from '@fluentui/react-components';
import type { FluentProviderProps } from '@fluentui/react-provider';

{
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Integration tests to cover type issues caught in our partners' codebases.

@dmytrokirpa dmytrokirpa changed the title chore: slot api support for react 17/18 feat: add support for react 18 May 26, 2025
// but there's no way to retrieve the element type of a slot from the slot definition
// right now without using SLOT_ELEMENT_TYPE_SYMBOL
// TODO: create a method to retrieve the element type of a slot
// eslint-disable-next-line @typescript-eslint/no-deprecated
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: lets make this scoped so its immediately obvious which symbol is being referred here ( I assume components ) ?

const { 
heading: root = {}, 
action, 
  // We should not use components to pass along the base element type of a slot
  // but there's no way to retrieve the element type of a slot from the slot definition
  // right now without using SLOT_ELEMENT_TYPE_SYMBOL
  // TODO: create a method to retrieve the element type of a slot
  // eslint-disable-next-line @typescript-eslint/no-deprecated
components 
} = state;

state.components?.[slotName] === undefined || typeof state.components[slotName] === 'string'
? asProp || state.components?.[slotName] || 'div'
: state.components[slotName]
state.components?.[slotName] === undefined || // eslint-disable-line @typescript-eslint/no-deprecated
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
state.components?.[slotName] === undefined || // eslint-disable-line @typescript-eslint/no-deprecated
// eslint-disable-next-line @typescript-eslint/no-deprecated
state.components?.[slotName] === undefined ||

@@ -22,6 +22,6 @@ const metadata: Meta<typeof Breadcrumb> = {
},
},
},
};
} as Meta<typeof Breadcrumb>;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Needed for React 18 to workaround the subcomponents Storybook type issue storybookjs/storybook#23170

@github-actions github-actions bot added the CI label May 27, 2025
@dmytrokirpa dmytrokirpa marked this pull request as ready for review May 28, 2025 16:09
@Hotell Hotell changed the title feat: add support for react 18 feat(react-components): add react 18 support May 28, 2025
@alexgorbatchev
Copy link

Lets go! Woohoo!! 🥳

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
4 participants