-
Notifications
You must be signed in to change notification settings - Fork 820
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
React 19 compatibility #2934
React 19 compatibility #2934
Conversation
Accordion.propTypes = { | ||
type(props) { | ||
const value = props.value || props.defaultValue; | ||
if (props.type && !['single', 'multiple'].includes(props.type)) { | ||
return new Error( | ||
'Invalid prop `type` supplied to `Accordion`. Expected one of `single | multiple`.' | ||
); | ||
} | ||
if (props.type === 'multiple' && typeof value === 'string') { | ||
return new Error( | ||
'Invalid prop `type` supplied to `Accordion`. Expected `single` when `defaultValue` or `value` is type `string`.' | ||
); | ||
} | ||
if (props.type === 'single' && Array.isArray(value)) { | ||
return new Error( | ||
'Invalid prop `type` supplied to `Accordion`. Expected `multiple` when `defaultValue` or `value` is type `string[]`.' | ||
); | ||
} | ||
return null; | ||
}, | ||
}; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prop types have been deprecated for a while and are now completely removed
https://react.dev/blog/2024/04/25/react-19-upgrade-guide#removed-deprecated-react-apis
Using TypeScript already handles this
if ((maxProp || maxProp === 0) && !isValidMaxNumber(maxProp)) { | ||
console.error(getInvalidMaxError(`${maxProp}`, 'Progress')); | ||
} | ||
|
||
const max = isValidMaxNumber(maxProp) ? maxProp : DEFAULT_MAX; | ||
|
||
if (valueProp !== null && !isValidValueNumber(valueProp, max)) { | ||
console.error(getInvalidValueError(`${valueProp}`, 'Progress')); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Relocated the logic from propTypes
that TypeScript can't help with
Separator.propTypes = { | ||
orientation(props, propName, componentName) { | ||
const propValue = props[propName]; | ||
const strVal = String(propValue); | ||
if (propValue && !isValidOrientation(propValue)) { | ||
return new Error(getInvalidOrientationError(strVal, componentName)); | ||
} | ||
return null; | ||
}, | ||
}; | ||
|
||
/* -----------------------------------------------------------------------------------------------*/ | ||
|
||
// Split this out for clearer readability of the error message. | ||
function getInvalidOrientationError(value: string, componentName: string) { | ||
return `Invalid prop \`orientation\` of value \`${value}\` supplied to \`${componentName}\`, expected one of: | ||
- horizontal | ||
- vertical | ||
Defaulting to \`${DEFAULT_ORIENTATION}\`.`; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TypeScript already handles this
|
||
if (!label.trim()) { | ||
console.error( | ||
`Invalid prop \`label\` supplied to \`${PROVIDER_NAME}\`. Expected non-empty \`string\`.` | ||
); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Relocated from propTypes
|
||
if (!altText.trim()) { | ||
console.error( | ||
`Invalid prop \`altText\` supplied to \`${ACTION_NAME}\`. Expected non-empty \`string\`.` | ||
); | ||
return null; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Relocated from propTypes
// Before React 19 accessing `element.props.ref` will throw a warning and suggest using `element.ref` | ||
// After React 19 accessing `element.ref` does the opposite | ||
// https://github.com/facebook/react/pull/28348 | ||
// | ||
// Access the ref using the method that doesn't yield a warning | ||
function getElementRef(element: React.ReactElement) { | ||
// Pre React 19 there's a getter on `element.props.ref` that throws a warning when attempting to access it. | ||
// This is safe to rely on. (As in... obviously, old React versions won't change). | ||
// https://github.com/facebook/react/blob/408258268edb5acdfdbf77bc6e0b0dc6396c0e6f/packages/react/src/jsx/ReactJSXElement.js#L89-L99 | ||
const getter = Object.getOwnPropertyDescriptor(element.props, 'ref')?.get; | ||
const hasPropWarning = getter && 'isReactWarning' in getter && getter.isReactWarning; | ||
|
||
if (hasPropWarning) { | ||
return (element as any).ref; | ||
} | ||
|
||
return element.props.ref; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ref access instance 1 of 2
A bit messy checking for the getter presence, but a naïve attempt to access either element.props.ref
or element.ref
directly like in #2811 yields a warning pre React 19
// Temporary while we await merge of this fix: | ||
// https://github.com/DefinitelyTyped/DefinitelyTyped/pull/55396 | ||
// prettier-ignore | ||
type PropsWithoutRef<P> = P extends any ? ('ref' extends keyof P ? Pick<P, Exclude<keyof P, 'ref'>> : P) : P; | ||
type ComponentPropsWithoutRef<T extends React.ElementType> = PropsWithoutRef< | ||
React.ComponentProps<T> | ||
>; | ||
|
||
type Primitives = { [E in typeof NODES[number]]: PrimitiveForwardRefComponent<E> }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This stuff is integrated into @types/react
packages/react/slot/src/Slot.tsx
Outdated
// Before React 19 accessing `element.props.ref` will throw a warning and suggest using `element.ref` | ||
// After React 19 accessing `element.ref` does the opposite | ||
// https://github.com/facebook/react/pull/28348 | ||
// | ||
// Access the ref using the method that doesn't yield a warning | ||
function getElementRef(element: React.ReactElement) { | ||
// Pre React 19 there's a getter on `element.props.ref` that throws a warning when attempting to access it. | ||
// This is safe to rely on. (As in... obviously, old React versions won't change). | ||
// https://github.com/facebook/react/blob/408258268edb5acdfdbf77bc6e0b0dc6396c0e6f/packages/react/src/jsx/ReactJSXElement.js#L89-L99 | ||
const getter = Object.getOwnPropertyDescriptor(element.props, 'ref')?.get; | ||
const hasPropWarning = getter && 'isReactWarning' in getter && getter.isReactWarning; | ||
|
||
if (hasPropWarning) { | ||
return (element as any).ref; | ||
} | ||
|
||
return element.props.ref; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ref access instance 2 of 2
// @ts-ignore | ||
ref: forwardedRef ? composeRefs(forwardedRef, childrenRef) : childrenRef, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Setting the ref
property has always yielded a type error, just didn't break the build before
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder what's the future of the Slot component as React considers cloneElement
a legacy api
. 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder what's the future of the Slot component as React considers cloneElement a legacy api. 🤔
We'll see—I haven't seen a good alternative there, and "legacy" doesn't mean it's deprecated yet. I think it will stay around for a while.
810b590
to
d8f2071
Compare
fbed4de
to
80a94d7
Compare
80a94d7
to
f1f02eb
Compare
// https://github.com/facebook/react/pull/28348 | ||
// | ||
// Access the ref using the method that doesn't yield a warning. | ||
function getElementRef(element: React.ReactElement) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤯
Should we extract this to a utility and reuse in other places instead of having the duplicated code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would have to be a separate package—I'd say we can keep it like this for now and extract it later if there's 3+ uses. I think two copy-pastes is OK for now.
// @ts-ignore | ||
ref: forwardedRef ? composeRefs(forwardedRef, childrenRef) : childrenRef, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder what's the future of the Slot component as React considers cloneElement
a legacy api
. 🤔
// https://github.com/facebook/react/pull/28348 | ||
// | ||
// Access the ref using the method that doesn't yield a warning. | ||
function getElementRef(element: React.ReactElement) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same method as before, let's extract to a utility.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like we could save bundle size here. How about going with
return React.version.split('.')[0] >= '19' ? element.props.ref : element.ref;
or
return element.props.propertyIsEnumerable('ref') ? element.props.ref : element.ref;
for this method? It seems to pass the tests.
Amazing work, how can I test ? some RC ? |
@CarlosZiegler yes, the latest RC versions include this PR |
ref
from props when possiblepropTypes
usageComponentPropsWithoutRef
type with the built-in React typeHandy link:
https://react.dev/blog/2024/04/25/react-19-upgrade-guide