-
Notifications
You must be signed in to change notification settings - Fork 47.2k
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
Fix: Class components should "consume" ref prop #28719
Conversation
Comparing: 8f55a6a...bc1fac0 Critical size changesIncludes critical production bundles, as well as any change greater than 2%:
Significant size changesIncludes any change greater than 0.2%: Expand to show |
ec53821
to
a550570
Compare
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.
Without this change, much existing code will break because the ref will attach to the inner component instead of the outer one.
Actually worse, it'd assign the ref to both. A callback ref would actually observe both bindings.
const prevState = current.memoizedState; | ||
// We could update instance props and state here, | ||
// but instead we rely on them being set during last render. | ||
// TODO: revisit this when we implement resuming. | ||
if (__DEV__) { | ||
if ( | ||
finishedWork.type === finishedWork.elementType && | ||
!finishedWork.type.defaultProps && |
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 disables the re-assign props warning when defaultProps are present? Seems fine.
72e8cf4
to
6450fc6
Compare
There are a few places in the reconciler where we modify the fiber's internal props object before passing it to userspace. The trickiest one is class components, because the props object gets exposed in many different places, including as a property on the class instance. This was already accounted for when we added support for setting default props on a lazy wrapper (i.e. React.lazy that resolves to a class component). In all of these same places, we will also need to remove the ref prop when enableRefAsProp is on. As a first step, this adds a new function, resolveClassComponentProps, where both default prop resolution and ref prop removal will happen.
When a ref is passed to a class component, the class instance is attached to the ref's current property automatically. This different from function components, where you have to do something extra to attach a ref to an instance, like passing the ref to `useImperativeHandle`. Existing class component code is written with the assumption that a ref will not be passed through as a prop. For example, class components that act as indirections often spread `this.props` onto a child component. To maintain this expectation, we should remove the ref from the props object ("consume" it) before passing it to lifecycle methods. Without this change, much existing code will break because the ref will attach to the inner component instead of the outer one. This is not an issue for function components because we used to warn if you passed a ref to a function component. Instead, you had to use `forwardRef`, which also implements this "consuming" behavior. Co-authored-by: Jan Kassens <jan@kassens.net>
Noticed these once I enabled class prop resolution in more places. Technically this was already observable if you wrapped a class with `React.lazy` and gave that wrapper default props, but since that was so rare it was never reported.
6450fc6
to
bc1fac0
Compare
When a ref is passed to a class component, the class instance is attached to the ref's current property automatically. This different from function components, where you have to do something extra to attach a ref to an instance, like passing the ref to `useImperativeHandle`. Existing class component code is written with the assumption that a ref will not be passed through as a prop. For example, class components that act as indirections often spread `this.props` onto a child component. To maintain this expectation, we should remove the ref from the props object ("consume" it) before passing it to lifecycle methods. Without this change, much existing code will break because the ref will attach to the inner component instead of the outer one. This is not an issue for function components because we used to warn if you passed a ref to a function component. Instead, you had to use `forwardRef`, which also implements this "consuming" behavior. There are a few places in the reconciler where we modify the fiber's internal props object before passing it to userspace. The trickiest one is class components, because the props object gets exposed in many different places, including as a property on the class instance. This was already accounted for when we added support for setting default props on a lazy wrapper (i.e. `React.lazy` that resolves to a class component). In all of these same places, we will also need to remove the ref prop when `enableRefAsProp` is on. Closes #28602 --------- Co-authored-by: Jan Kassens <jan@kassens.net> DiffTrain build for [dc545c8](dc545c8)
Same as facebook#28719 but for SSR.
Same as facebook#28719 but for SSR.
When a ref is passed to a class component, the class instance is attached to the ref's current property automatically. This different from function components, where you have to do something extra to attach a ref to an instance, like passing the ref to `useImperativeHandle`. Existing class component code is written with the assumption that a ref will not be passed through as a prop. For example, class components that act as indirections often spread `this.props` onto a child component. To maintain this expectation, we should remove the ref from the props object ("consume" it) before passing it to lifecycle methods. Without this change, much existing code will break because the ref will attach to the inner component instead of the outer one. This is not an issue for function components because we used to warn if you passed a ref to a function component. Instead, you had to use `forwardRef`, which also implements this "consuming" behavior. There are a few places in the reconciler where we modify the fiber's internal props object before passing it to userspace. The trickiest one is class components, because the props object gets exposed in many different places, including as a property on the class instance. This was already accounted for when we added support for setting default props on a lazy wrapper (i.e. `React.lazy` that resolves to a class component). In all of these same places, we will also need to remove the ref prop when `enableRefAsProp` is on. Closes facebook#28602 --------- Co-authored-by: Jan Kassens <jan@kassens.net>
Same as facebook#28719 but for SSR.
When a ref is passed to a class component, the class instance is attached to the ref's current property automatically. This different from function components, where you have to do something extra to attach a ref to an instance, like passing the ref to `useImperativeHandle`. Existing class component code is written with the assumption that a ref will not be passed through as a prop. For example, class components that act as indirections often spread `this.props` onto a child component. To maintain this expectation, we should remove the ref from the props object ("consume" it) before passing it to lifecycle methods. Without this change, much existing code will break because the ref will attach to the inner component instead of the outer one. This is not an issue for function components because we used to warn if you passed a ref to a function component. Instead, you had to use `forwardRef`, which also implements this "consuming" behavior. There are a few places in the reconciler where we modify the fiber's internal props object before passing it to userspace. The trickiest one is class components, because the props object gets exposed in many different places, including as a property on the class instance. This was already accounted for when we added support for setting default props on a lazy wrapper (i.e. `React.lazy` that resolves to a class component). In all of these same places, we will also need to remove the ref prop when `enableRefAsProp` is on. Closes #28602 --------- Co-authored-by: Jan Kassens <jan@kassens.net> DiffTrain build for commit dc545c8.
When a ref is passed to a class component, the class instance is attached to the ref's current property automatically. This different from function components, where you have to do something extra to attach a ref to an instance, like passing the ref to
useImperativeHandle
.Existing class component code is written with the assumption that a ref will not be passed through as a prop. For example, class components that act as indirections often spread
this.props
onto a child component. To maintain this expectation, we should remove the ref from the props object ("consume" it) before passing it to lifecycle methods. Without this change, much existing code will break because the ref will attach to the inner component instead of the outer one.This is not an issue for function components because we used to warn if you passed a ref to a function component. Instead, you had to use
forwardRef
, which also implements this "consuming" behavior.There are a few places in the reconciler where we modify the fiber's internal props object before passing it to userspace. The trickiest one is class components, because the props object gets exposed in many different places, including as a property on the class instance.
This was already accounted for when we added support for setting default props on a lazy wrapper (i.e.
React.lazy
that resolves to a class component).In all of these same places, we will also need to remove the ref prop when
enableRefAsProp
is on.Closes #28602