-
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
Add forwardRef DEV warning for prop-types on render function #12644
Conversation
Details of bundled changes.Comparing: 0887c7d...4a64257 react
Generated by 🚫 dangerJS |
I encountered this same issue, and was surprised that defaultProps and propTypes did not work for a forwardRef-wrapped render function. @bvaughn Is there a reason why the defaultProps and propTypes could not be reassigned to the resulting component? |
Hopefully the recently added warning will help with this 😄 There's a couple of reasons why The thing being returned might not be a functional or class component. It could be e.g. a The author of the |
20: Update react monorepo to v16.4.0 r=renovate[bot] a=renovate[bot] This Pull Request renovates the package group "react monorepo". - [react-dom](https://github.com/facebook/react) (`dependencies`): from `16.3.2` to `16.4.0` - [react](https://github.com/facebook/react) (`dependencies`): from `16.3.2` to `16.4.0` # Release Notes <details> <summary>facebook/react</summary> ### [`v16.4.0`](https://github.com/facebook/react/blob/master/CHANGELOG.md#​1640-May-23-2018) [Compare Source](facebook/react@8e5f12c...v16.4.0) ##### React * Add a new [experimental](`https://github.com/reactjs/rfcs/pull/51`) `React.unstable_Profiler` component for measuring performance. ([@​bvaughn] in [#​12745](`https://github.com/facebook/react/pull/12745`)) ##### React DOM * Add support for the Pointer Events specification. ([@​philipp-spiess] in [#​12507](`https://github.com/facebook/react/pull/12507`)) * Properly call `getDerivedStateFromProps()` regardless of the reason for re-rendering. ([@​acdlite] in [#​12600](`https://github.com/facebook/react/pull/12600`) and [#​12802](`https://github.com/facebook/react/pull/12802`)) * Fix a bug that prevented context propagation in some cases. ([@​gaearon] in [#​12708](`https://github.com/facebook/react/pull/12708`)) * Fix re-rendering of components using `forwardRef()` on a deeper `setState()`. ([@​gaearon] in [#​12690](`https://github.com/facebook/react/pull/12690`)) * Fix some attributes incorrectly getting removed from custom element nodes. ([@​airamrguez] in [#​12702](`https://github.com/facebook/react/pull/12702`)) * Fix context providers to not bail out on children if there's a legacy context provider above. ([@​gaearon] in [#​12586](`https://github.com/facebook/react/pull/12586`)) * Add the ability to specify `propTypes` on a context provider component. ([@​nicolevy] in [#​12658](`https://github.com/facebook/react/pull/12658`)) * Fix a false positive warning when using `react-lifecycles-compat` in `<StrictMode>`. ([@​bvaughn] in [#​12644](`https://github.com/facebook/react/pull/12644`)) * Warn when the `forwardRef()` render function has `propTypes` or `defaultProps`. ([@​bvaughn] in [#​12644](`https://github.com/facebook/react/pull/12644`)) * Improve how `forwardRef()` and context consumers are displayed in the component stack. ([@​sophiebits] in [#​12777](`https://github.com/facebook/react/pull/12777`)) * Change internal event names. This can break third-party packages that rely on React internals in unsupported ways. ([@​philipp-spiess] in [#​12629](`https://github.com/facebook/react/pull/12629`)) ##### React Test Renderer * Fix the `getDerivedStateFromProps()` support to match the new React DOM behavior. ([@​koba04] in [#​12676](`https://github.com/facebook/react/pull/12676`)) * Fix a `testInstance.parent` crash when the parent is a fragment or another special node. ([@​gaearon] in [#​12813](`https://github.com/facebook/react/pull/12813`)) * `forwardRef()` components are now discoverable by the test renderer traversal methods. ([@​gaearon] in [#​12725](`https://github.com/facebook/react/pull/12725`)) * Shallow renderer now ignores `setState()` updaters that return `null` or `undefined`. ([@​koba04] in [#​12756](`https://github.com/facebook/react/pull/12756`)) ##### React ART * Fix reading context provided from the tree managed by React DOM. ([@​acdlite] in [#​12779](`https://github.com/facebook/react/pull/12779`)) ##### React Call Return (Experimental) * This experiment was deleted because it was affecting the bundle size and the API wasn't good enough. It's likely to come back in the future in some other form. ([@​gaearon] in [#​12820](`https://github.com/facebook/react/pull/12820`)) ##### React Reconciler (Experimental) * The [new host config shape](https://github.com/facebook/react/blob/c601f7a64640290af85c9f0e33c78480656b46bc/packages/react-noop-renderer/src/createReactNoop.js#L82-L285) is flat and doesn't use nested objects. ([@​gaearon] in [#​12792](`https://github.com/facebook/react/pull/12792`)) --- </details> --- This PR has been generated by [Renovate Bot](https://renovatebot.com). Co-authored-by: Renovate Bot <bot@renovateapp.com>
164: Update react monorepo to v16.4.0 r=rehandalal a=renovate[bot] This Pull Request renovates the package group "react monorepo". - [react-dom](https://github.com/facebook/react) (`dependencies`): from `16.3.2` to `16.4.0` - [react](https://github.com/facebook/react) (`dependencies`): from `16.3.2` to `16.4.0` # Release Notes <details> <summary>facebook/react</summary> ### [`v16.4.0`](https://github.com/facebook/react/blob/master/CHANGELOG.md#​1640-May-23-2018) [Compare Source](facebook/react@8e5f12c...v16.4.0) ##### React * Add a new [experimental](`https://github.com/reactjs/rfcs/pull/51`) `React.unstable_Profiler` component for measuring performance. ([@​bvaughn] in [#​12745](`https://github.com/facebook/react/pull/12745`)) ##### React DOM * Add support for the Pointer Events specification. ([@​philipp-spiess] in [#​12507](`https://github.com/facebook/react/pull/12507`)) * Properly call `getDerivedStateFromProps()` regardless of the reason for re-rendering. ([@​acdlite] in [#​12600](`https://github.com/facebook/react/pull/12600`) and [#​12802](`https://github.com/facebook/react/pull/12802`)) * Fix a bug that prevented context propagation in some cases. ([@​gaearon] in [#​12708](`https://github.com/facebook/react/pull/12708`)) * Fix re-rendering of components using `forwardRef()` on a deeper `setState()`. ([@​gaearon] in [#​12690](`https://github.com/facebook/react/pull/12690`)) * Fix some attributes incorrectly getting removed from custom element nodes. ([@​airamrguez] in [#​12702](`https://github.com/facebook/react/pull/12702`)) * Fix context providers to not bail out on children if there's a legacy context provider above. ([@​gaearon] in [#​12586](`https://github.com/facebook/react/pull/12586`)) * Add the ability to specify `propTypes` on a context provider component. ([@​nicolevy] in [#​12658](`https://github.com/facebook/react/pull/12658`)) * Fix a false positive warning when using `react-lifecycles-compat` in `<StrictMode>`. ([@​bvaughn] in [#​12644](`https://github.com/facebook/react/pull/12644`)) * Warn when the `forwardRef()` render function has `propTypes` or `defaultProps`. ([@​bvaughn] in [#​12644](`https://github.com/facebook/react/pull/12644`)) * Improve how `forwardRef()` and context consumers are displayed in the component stack. ([@​sophiebits] in [#​12777](`https://github.com/facebook/react/pull/12777`)) * Change internal event names. This can break third-party packages that rely on React internals in unsupported ways. ([@​philipp-spiess] in [#​12629](`https://github.com/facebook/react/pull/12629`)) ##### React Test Renderer * Fix the `getDerivedStateFromProps()` support to match the new React DOM behavior. ([@​koba04] in [#​12676](`https://github.com/facebook/react/pull/12676`)) * Fix a `testInstance.parent` crash when the parent is a fragment or another special node. ([@​gaearon] in [#​12813](`https://github.com/facebook/react/pull/12813`)) * `forwardRef()` components are now discoverable by the test renderer traversal methods. ([@​gaearon] in [#​12725](`https://github.com/facebook/react/pull/12725`)) * Shallow renderer now ignores `setState()` updaters that return `null` or `undefined`. ([@​koba04] in [#​12756](`https://github.com/facebook/react/pull/12756`)) ##### React ART * Fix reading context provided from the tree managed by React DOM. ([@​acdlite] in [#​12779](`https://github.com/facebook/react/pull/12779`)) ##### React Call Return (Experimental) * This experiment was deleted because it was affecting the bundle size and the API wasn't good enough. It's likely to come back in the future in some other form. ([@​gaearon] in [#​12820](`https://github.com/facebook/react/pull/12820`)) ##### React Reconciler (Experimental) * The [new host config shape](https://github.com/facebook/react/blob/c601f7a64640290af85c9f0e33c78480656b46bc/packages/react-noop-renderer/src/createReactNoop.js#L82-L285) is flat and doesn't use nested objects. ([@​gaearon] in [#​12792](`https://github.com/facebook/react/pull/12792`)) --- </details> --- This PR has been generated by [Renovate Bot](https://renovatebot.com). Co-authored-by: Renovate Bot <bot@renovateapp.com>
I don't think the argument regarding the eventual result of the I do agree that there is an alternative formulation available here for the component author that does allow for the desired result, but I have two concerns with the current situation:
Would you be open for a PR fixing this, or are there murkier depths that I'm not considering here? |
This sounds like an omission to me. Let’s fix that? |
Maybe we're saying different things? I'll attempt to clarify. This works. (Prop type warnings will fire for function InnerComponent(props) { /* ... */ }
InnerComponent.propTypes = {
foo: PropTypes.string.isRequired
};
const OuterComponent = React.forwardRef(
(props, ref) => <InnerComponent ref={ref} {...props} />
); This is intentionally not supported: function InnerComponent(props) { /* ... */ }
const OuterComponent = React.forwardRef(
(props, ref) => <InnerComponent ref={ref} {...props} />
);
OuterComponent.propTypes = {
foo: PropTypes.string.isRequired
}; External code shouldn't be overriding import { Link } from 'react-router-dom';
Link.propTypes = { /* ... */ }; Attributes like
This is required in order to not break backwards compatibility. I know reading old RFCs is a lot of work, but the necessity of this was considered and discussed in the ref forwarding RFC. (You might be interested in checking out that portion of the RFC at least! 😄) |
Yeah, I think we're talking about slightly different things. What I would like to work is this (note the two args of const Componentish = (props, ref) => (<div {...props} ref={ref} />)
Componentish.propTypes = { foo: PropTypes.string.isRequired, bar: PropTypes.string }
Componentish.defaultProps = { bar: 'BAZ' }
export default React.forwardRef(Componentish); At the moment, that produces the warning of this PR, but as a developer I'd prefer if instead it "just worked", which I believe would be possible with something like this backwards-compatible solution (not meant to be production code, just a sketch of what I mean): --- a/packages/react/src/ReactElement.js
+++ b/packages/react/src/ReactElement.js
@@ -219,8 +219,8 @@ export function createElement(type, config, children) {
}
// Resolve default props
- if (type && type.defaultProps) {
- const defaultProps = type.defaultProps;
+ const defaultProps = type && (type.defaultProps || (type.render && type.render.defaultProps));
+ if (defaultProps) {
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
--- a/packages/react/src/ReactElementValidator.js
+++ b/packages/react/src/ReactElementValidator.js
@@ -213,11 +213,19 @@ function validateChildKeys(node, parentType) {
* @param {ReactElement} element
*/
function validatePropTypes(element) {
- const componentClass = element.type;
+ let componentClass = element.type;
if (typeof componentClass !== 'function') {
- return;
+ if (
+ typeof componentClass === 'object' &&
+ componentClass !== null &&
+ typeof componentClass.render === 'function'
+ ) {
+ componentClass = componentClass.render;
+ } else {
+ return;
+ }
}
- const name = componentClass.displayName || componentClass.name;
+ const name = getComponentName(element)
const propTypes = componentClass.propTypes;
if (propTypes) {
currentlyValidatingElement = element; I have read through the RFC discussion, and I don't think that this option was considered. Now, whether it's an option that could be considered is something I think you might be more qualified to answer. :) |
I see. Thank you for clarifying. 😄 It still seems to me like you should be able to accomplish what you're trying to do like this: const Component = ({ forwardedRef, ...props }) => <div {...props} ref={ref} />;
Component.propTypes = {
foo: PropTypes.string.isRequired,
bar: PropTypes.string
};
Component.defaultProps = { bar: "BAZ" };
export default React.forwardRef((props, ref) => (
<Component {...props} forwardedRef={ref} />
)); Is there a reason this doesn't work? (Why do the |
Sure, that does work. I mentioned a couple of concerns with this earlier, but really they boil down to the argument |
@piuccio The thing you put into This should work: function MyComponent(props) {}
MyComponent.propTypes = {};
export default React.forwardRef((props, ref) => <MyComponent {...props} forwardedRef={ref} />); In the next release we'll make this work too: function renderComponent(props, ref) {}
const MyComponent = React.forwardRef(renderComponent);
MyComponent.propTypes = {};
export default MyComponent; (But it doesn't work yet.) |
Just wanted to add that the warning will remain for this case too, since it will continue to not work 😄 Just to recap: function MyComponent(props) {
// ...
}
MyComponent.propTypes = {}; // This will work now
function renderFunction(props, ref) {
return <MyComponent {...props} forwardedRef={ref} />;
}
renderFunction.propTypes = {}; // This WILL NOT work
const ExportedComponent = React.forwardRef(renderFunction);
ExportedComponent.propTypes = {}; // This will work in the next release |
Thanks, I've fixed my code. It would also be nice to mention in the documentation that |
It's not. It is an |
Well, if you use So for plain users like me, I guess Even if the shape is not too important, having it in documentation will help people understand where to defined the |
@piuccio I misread your original message as "forwardRef" rather than "forwardedRef". Sorry for any confusion my response caused. That being said, I'm not quite sure what you're saying. The If you want to specify it as one of the prop-types for the inner component, I would suggest using So maybe something like this would work for you, at a high level? const sharedPropTypes = {
bar: PropType.string.isRequired,
foo: PropType.string.isRequired
};
const SomeComponent = ({ bar, foo, forwardedRef }) => {
// Your component here...
};
SomeComponent.propTypes = {
...sharedPropTypes,
forwardedRef: PropTypes.any
};
const RefForwardingWrapper = React.forwardRef((props, ref) => (
<SomeComponent {...props} forwardedRef={ref} />
));
RefForwardingWrapper.propTypes = sharedPropTypes; |
Someone at FB recently reported a bug that boiled down to the following:
This kind of worked but, of course, prop-types didn't work.
What they meant to do was:
I think it would be nice for us to warn about this in DEV mode. I'm not enamored with this warning message in particular though if someone has better suggestions.