Repository: jsx-eslint/eslint-plugin-react (JavaScript)

Issue:
Rule proposal: react/no-unstable-nested-components
I'd like to introduce a new rule for detecting React components which are declared inside another component or inside class component's render block. I was surprised to see this rule doesn't exist yet.

I've named this rule `react/no-unstable-nested-components` - if anyone can come up with a better name I'm open for it.

I'll set up the PR soon. Here's the implementation [AriPerkkio/eslint-plugin-react/tree/react/no-unstable-nested-components](https://github.com/AriPerkkio/eslint-plugin-react/tree/react/no-unstable-nested-components).

## Use case
Prevent developers from creating components inside another components.

### 1. Performance
```jsx
function Component() {
  function UnstableNestedComponent() {
    return <div />;
  }

  return (
    <div>
      <UnstableNestedComponent />
    </div>
  );
}
```

When `Component` re-renders (e.g. parent state updates) the `UnstableNestedComponent` is recreated. React reconciliation will compare the previous render's `UnstableNestedComponent` and the current one using [reference equality](https://github.com/facebook/react/blob/v16.13.1/packages/react-reconciler/src/ReactChildFiber.js#L407). Since the reference does not match, the element will be completely recreated - not to be confused with re-rendering. I guess this is a small performance optimization, especially in cases where this pattern is used on application root level.

Since all the children of the nested component are also recreated on each re-render, optimization provided by `React.PureComponent` is gone. The `PureComponent` is recreated on each re-render.
```jsx
class PureComponent extends React.PureComponent {
  render() {
    return <div />;
  };
};

function Component(props) {
  function Wrapper() {
    return <div>{props.children}</div>;
  };

  return (
    <div>
      <Wrapper />
    </div>
  )
};

...

<Component>
  <PureComponent />
</Component>
```

### 2. Prevent unnoticeable bugs

This is the most important use case of this rule. In addition to performance optimization this rule will prevent [possible bugs](https://codepen.io/ariperkkio/pen/vYLodLB).

In cases where the nested component is rendering given **stateful** children, each re-render will reset the children's state back to the initial state. Without this rule these cases are easily unnoticeable and cause weird behavior in apps.

```jsx
function Accordion(props) {
  const [isOpen, toggle] = React.useReducer(s => !s, false);

  return (
    <>
      <button onClick={toggle}>Toggle</button>
      <div style={{ opacity: isOpen ? 1 : 0 }}>
        {props.children}
      </div>
    </>
  );
};

function Menu(props) {
  function MenuWrapper() {
    return <div>{props.children}</div>;
  };

  return (
    <div>
      <h2>Menu</h2>
      <MenuWrapper />
    </div>
  );
};

export default function Navigation() {
  return (
    <nav>
      <button onClick={React.useReducer(() => [], [])[1]}>
        Force render
      </button>
      <Menu>
        <Accordion>About us</Accordion>
        <Accordion>Contact</Accordion>
      </Menu>
    </nav>
  );
};
```

In example above:

1. Click About us to open the "accordion"
2. Click force render to update parent state and to trigger re-render

- Expected: Accordion stays open
- Actual: Accordion closes. Component is recreated and initial state is set.

## Rule Details

The following patterns are considered warnings:

```jsx
function Component() {
  function UnstableNestedComponent() {
    return <div />;
  }

  return (
    <div>
      <UnstableNestedComponent />
    </div>
  );
}
```

```jsx
class Component extends React.Component {
  render() {
    function UnstableNestedComponent() {
      return <div />;
    }

    return (
      <div>
        <UnstableNestedComponent />
      </div>
    );
  }
}
```

The following patterns are **not** considered warnings:

```jsx
function OutsideDefinedComponent(props) {
  return <div />;
}

function Component() {
  return (
    <div>
      <OutsideDefinedComponent />
    </div>
  );
}
```

```jsx
function Component() {
  const MemoizedNestedComponent = React.useCallback(() => <div />, []);

  return (
    <div>
      <MemoizedNestedComponent />
    </div>
  );
}
```

## Rule Options

```js
...
"react/no-unstable-nested-components": "off" | "warn" | "error"
...
```



Discussion:
While I agree with the principle of this rule, component detection is very difficult, and this will likely result in a lot of false positives.
I'm starting to realize the difficulty of component detection now. At first I though the `Components.detect(..` would detect most cases, but no.

I've ran this linter to material-ui, ant-design and some other big repositories. I still need to add rule for detecting components declared inside props, e.g. `<Component footer={() => <div />} />`.

Also it seems that renderProps have to be excluded from the rule. I'm not exactly sure how React is handling the renderProps but for some reason they are not causing the _children losing state_ issue.

```jsx
function Menu(props) {
    const Footer = props.footer;
    return (
        <div>
            <h2>Menu</h2>
            {props.children({})}
            <Footer />
        </div>
    );
};

export default function Navigation() {
    return (
        <nav>
            <button onClick={React.useReducer(() => [], [])[1]}>
                Force render
        </button>
            <Menu footer={() => <Accordion>Footer</Accordion>}>
                {(renderProps) => {
                    return (
                        <>
                            <Accordion>About us</Accordion>
                            <Accordion>Contact</Accordion>
                        </>
                    );
                }}
            </Menu>
        </nav>
    );
};
```

In the example above only Footer will hide when force render is clicked. Components inside renderProps remain their state.
render props aren't components, and are usually *called*, not used in an element context, so they don't *have* any state.
>  _called_, not used in an element context.

Thanks, that was exactly the case here. The returned element will always be the same React.Fragment as _a stable reference_.

After adding detection for component inside prop (`<Component footer={() => <div />} />`), a lot of errors came out from various repositories. Now the detection will exclude props which names are prefixed with render, e.g. `<Component renderFooter={() => <div />}`. This seems to be quite common pattern. 

There's also an option which can be used to disable "component in prop" warnings.

---

## Rule Options

```js
...
"react/no-unstable-nested-components": [
  "off" | "warn" | "error",
  { "allowAsProps": true | false }
]
...
```

You can allow component creation inside component props by setting `allowAsProps` option to true. When using this option make sure you are **calling** the props in the receiving component and not using them as elements.

The following patterns are **not** considered warnings:

```jsx
function SomeComponent(props) {
  return <div>{props.footer()}</div>;
}

function Component() {
  return (
    <div>
      <SomeComponent footer={() => <div />} />
    </div>
  );
}
```
---

I've tested this plugin with following repositories successfully.

- [Ant design](https://github.com/ant-design/ant-design)
- [Material UI](https://github.com/mui-org/material-ui)
- [Jira Clone](https://github.com/oldboyxx/jira_clone)
- [Reach UI](https://github.com/reach/reach-ui)
- [React Bootstrap](https://github.com/react-bootstrap/react-bootstrap/)

@ljharb can you think of any edge cases where these false positives could easily happen? I've added quite many test cases to the PR now.

It very much will *not* always be a "stable reference"; a new fragment is created every time it's called, and a new function is passed to the `Menu` component every time `Navigation` renders. In other words, you have the most unstable reference you could possibly create.
I think we are not talking about the same thing here, at least in this context. The only thing that matters is that element type is stable. 

```jsx
function Menu(props) {
  const Footer = props.footer;
  return (
    <div>
      {props.children()}
      <Footer />
    </div>
  );
}

function Component() {
  return (
    <Menu
      footer={function SomeFooter() {
        return <>Footer</>;
      }}
    >
      {function Wrapper() {
        return (
          <>
            <div>About</div>
            <div>Contact</div>
          </>
        );
      }}
    </Menu>
  );
}
```

React reconciler will compare Footer as

- `function SomeFooter() === function SomeFooter()` -> False
- Element type has changed, lets create element again. Destroy all children and start over.

And the `props.children` as

- React.Fragment === React.Fragment -> True
- Reuse same element, no need to destroy children.
- It won't compare Wrapper since given prop is **called**

It doesn't really matter how many times new fragment is recreated. As long as the element type keeps stable.
```js
current.elementType === element.type
```
https://github.com/facebook/react/blob/da834083cccb6ef942f701c6b6cecc78213196a8/packages/react-reconciler/src/ReactChildFiber.js#L407 

OK - so by "stable" or "unstable" here, you mean, you want a rule that will warn on any element type (ie, something used as the tag name in jsx, or as the first arg to `React.createElement`) whose identity can change across renders?
Yes, exactly. I'm not sure how `React.createElement` could be detected though. I've never been using that API directly. 
The current PR will detect these cases from JSX only.
There's a bunch of examples of existing rules that do that; rules must only be jsx-specific whenever possible.
Support for `React.createElement` cases added. Tested rule against same repositories mentioned earlier.
In order to identify false positives I've created [a tool](https://github.com/AriPerkkio/eslint-remote-tester) for linting 100's of repositories easily. After scanning [165 repositories](https://github.com/AriPerkkio/eslint-plugin-react/blob/react/no-unstable-nested-components-test/eslint-remote-tester.config.js#L3-L170) against this rule I've identified one false positive caused by `useEffect`'s clean-up function,. 
Also it looks like `allowAsProps` detection could be improved. Some components created inside props are not marked with correct error message at the moment. 

Here are all the results:
- [Default settings](https://github.com/AriPerkkio/eslint-plugin-react/tree/react/no-unstable-nested-components-test/eslint-remote-tester-results-default)
- [allowAsProps: true](https://github.com/AriPerkkio/eslint-plugin-react/tree/react/no-unstable-nested-components-test/eslint-remote-tester-results-allowAsProps)
Another common issue we faced while working on nested components is that hooks of the parent component are sometimes used directly in the nested component. This caused a very hard to identify [issue](https://github.com/react-hook-form/react-hook-form/discussions/1744#discussioncomment-20578) in our component

```jsx
const App = () => {

  const [count, setCount] = useState(0)

  const myInternalComponent = () => {
    return ( <div>{count}</div> ) // this will cause issues in some scenarios
  }

  return (
    <div>
      {myInternalComponent()}
    </div>
  )
}
```
Thanks for the comment @DaniAkash. These real-world examples are really valuable.

Based on the [codesandbox example](https://codesandbox.io/s/stupefied-poitras-11y82?file=/src/App.js) there is a nested component `<EditModal />`. This rule will flag that as invalid pattern and mark it as error. Without studying the issue thoroughly, my guess is that the state of `<EditModal />`'s children is reset during every re-render. 

But the pattern of your comment would not be marked as error since element type returned from `{myInternalComponent()}` is always `div`. React will not re-construct the component tree when element type is stable.  Component below however would be considered as an error:

```js
const App = () => {
  const [count, setCount] = useState(0)

  const MyInternalComponent = () => <div>{count}</div>;

  return (
    <div>
      <MyInternalComponent />
    </div>
  )
}
```
Yeah, If the internal component has its own state, it leads to a messy situation

```jsx
const App = () => {
  const [count, setCount] = useState(0)

  const MyInternalComponent = () => {
    const [internalCount, setInternalCount] = useState(0) // will reset to zero on re-render of App
    return <div>{internalCount}</div>
  };

  return (
    <div>
      <MyInternalComponent />
    </div>
  )
}
```
Obviously it's going to be about impossible to detect cases where a new component type is being passed in as a prop, but this looks like a very useful rule overall.
The initial post is not up-to-date with current implementation of the PR. 

By default this rule will warn if a component is passed as prop and the prop name doesn't start with `render`, e.g. `renderHeader`. If component name doesn't start with render it is expected that developer is using the prop as element, e.g. `({ header: Header }) => <Header />`. 

Here's the current implementation of rule documentation:  

> By default component creation is allowed inside component props only if prop name starts with `render`. See `allowAsProps` option for disabling this limitation completely.

```jsx
function SomeComponent(props) {
  return <div>{props.renderFooter()}</div>;
}
function Component() {
  return (
    <div>
      <SomeComponent renderFooter={() => <div />} />
    </div>
  );
}
```

## Rule Options

```js
...
"react/no-unstable-nested-components": [
  "off" | "warn" | "error",
  { "allowAsProps": true | false }
]
...
```

> You can allow component creation inside component props by setting `allowAsProps` option to true. When using this option make sure you are **calling** the props in the receiving component and not using them as elements.
It may be easier to implement something with fewer false-negatives in some TS-only equivalent of this plugin? Or maybe we could begin making some rules that are only valid in TS environments?
Just to clarify better:

this is a problem:

`const MyComponent = () => <SomeStatefulComponent>`

but this is not a problem:

`const getMyComponent = () => <SomeStatefulComponent>`
@gaurav5430 that's correct. The first one can be used as React Element in JSX, the second one cannot. This rule will only pick up the first one. 
How could this be handled in cases like Tanstack Table v8 column definitions, where a function is expected to be passed to cell?


```
  const columns = useMemo(
    () => [
      {
        id: "actions",
        header: "Remove",
        accessorKey: "remove",
        size: 40,
        //  react/no-unstable-nested-components issue here
        cell: (data) => {
          return (
            <RowActionsCell
              data={data}
            />
          );
        },
      },
    ],
    [],
  );

```

https://github.com/TanStack/table/discussions/4184
Something like this:

```diff
const columns = useMemo(
  () => [
    {
      id: "actions",
      header: "Remove",
      accessorKey: "remove",
      size: 40,
-     //  react/no-unstable-nested-components issue here
-     cell: (data) => {
-       return (
-         <RowActionsCell
-           data={data}
-         />
-       );
-     },
+     cell: renderCell,
    },
  ],
  [],
);

+ function renderCell(data) {
+   return (
+     <RowActionsCell
+       data={data}
+     />
+   )
+ }
```


In your example would the `renderCell` function have to be moved outside of the component? If we kept it inside the component and used `useCallback`, would it be [the same scenario that it warns about in the rule details (described with the uppercase WARNING)](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-unstable-nested-components.md#rule-details)?
Yes, move it outside the component. `useCallback` and `useMemo` cannot guarantee that the component would **always** be created just once.
alternatively, just use concise method syntax instead of an arrow function.



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f3318c5f65..cb836c853a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
 * [`jsx-pascal-case`]: support minimatch `ignore` option ([#2906][] @bcherny)
 * [`jsx-pascal-case`]: support `allowNamespace` option ([#2917][] @kev-y-huang)
 * [`jsx-newline`]: Add prevent option ([#2935][] @jsphstls)
+* [`no-unstable-nested-components`]: Prevent creating unstable components inside components ([#2750][] @AriPerkkio)
 
 ### Fixed
 * [`jsx-no-constructed-context-values`]: avoid a crash with `as X` TS code ([#2894][] @ljharb)
@@ -47,6 +48,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
 [#2894]: https://github.com/yannickcr/eslint-plugin-react/issues/2894
 [#2893]: https://github.com/yannickcr/eslint-plugin-react/pull/2893
 [#2862]: https://github.com/yannickcr/eslint-plugin-react/pull/2862
+[#2750]: https://github.com/yannickcr/eslint-plugin-react/pull/2750
 
 ## [7.22.0] - 2020.12.29
 
@@ -3306,3 +3308,4 @@ If you're still not using React 15 you can keep the old behavior by setting the
 [`function-component-definition`]: docs/rules/function-component-definition.md
 [`jsx-newline`]: docs/rules/jsx-newline.md
 [`jsx-no-constructed-context-values`]: docs/rules/jsx-no-constructed-context-values.md
+[`no-unstable-nested-components`]: docs/rules/no-unstable-nested-components.md
\ No newline at end of file
diff --git a/README.md b/README.md
index e276ff5a11..929a8d5aee 100644
--- a/README.md
+++ b/README.md
@@ -137,6 +137,7 @@ Enable the rules that you would like to use.
 | ✔ |  | [react/no-unescaped-entities](docs/rules/no-unescaped-entities.md) | Detect unescaped HTML entities, which might represent malformed tags |
 | ✔ | 🔧 | [react/no-unknown-property](docs/rules/no-unknown-property.md) | Prevent usage of unknown DOM property |
 |  |  | [react/no-unsafe](docs/rules/no-unsafe.md) | Prevent usage of unsafe lifecycle methods |
+|  |  | [react/no-unstable-nested-components](docs/rules/no-unstable-nested-components.md) | Prevent creating unstable components inside components |
 |  |  | [react/no-unused-prop-types](docs/rules/no-unused-prop-types.md) | Prevent definitions of unused prop types |
 |  |  | [react/no-unused-state](docs/rules/no-unused-state.md) | Prevent definition of unused state fields |
 |  |  | [react/no-will-update-set-state](docs/rules/no-will-update-set-state.md) | Prevent usage of setState in componentWillUpdate |
@@ -179,7 +180,7 @@ Enable the rules that you would like to use.
 | ✔ |  | [react/jsx-key](docs/rules/jsx-key.md) | Report missing `key` props in iterators/collection literals |
 |  |  | [react/jsx-max-depth](docs/rules/jsx-max-depth.md) | Validate JSX maximum depth |
 |  | 🔧 | [react/jsx-max-props-per-line](docs/rules/jsx-max-props-per-line.md) | Limit maximum of props on a single line in JSX |
-|  | 🔧 | [react/jsx-newline](docs/rules/jsx-newline.md) | Require or prevent a new line after jsx elements and expressions |
+|  | 🔧 | [react/jsx-newline](docs/rules/jsx-newline.md) | Require or prevent a new line after jsx elements and expressions. |
 |  |  | [react/jsx-no-bind](docs/rules/jsx-no-bind.md) | Prevents usage of Function.prototype.bind and arrow functions in React component props |
 | ✔ |  | [react/jsx-no-comment-textnodes](docs/rules/jsx-no-comment-textnodes.md) | Comments inside children section of tag should be placed inside braces |
 |  |  | [react/jsx-no-constructed-context-values](docs/rules/jsx-no-constructed-context-values.md) | Prevents JSX context provider values from taking values that will cause needless rerenders. |
diff --git a/docs/rules/no-unstable-nested-components.md b/docs/rules/no-unstable-nested-components.md
new file mode 100644
index 0000000000..507d83d56e
--- /dev/null
+++ b/docs/rules/no-unstable-nested-components.md
@@ -0,0 +1,142 @@
+# Prevent creating unstable components inside components (react/no-unstable-nested-components)
+
+Creating components inside components without memoization leads to unstable components. The nested component and all its children are recreated during each re-render. Given stateful children of the nested component will lose their state on each re-render.
+
+React reconcilation performs element type comparison with [reference equality](https://github.com/facebook/react/blob/v16.13.1/packages/react-reconciler/src/ReactChildFiber.js#L407). The reference to the same element changes on each re-render when defining components inside the render block. This leads to complete recreation of the current node and all its children. As a result the virtual DOM has to do extra unnecessary work and [possible bugs are introduced](https://codepen.io/ariperkkio/pen/vYLodLB).
+
+## Rule Details
+
+The following patterns are considered warnings:
+
+```jsx
+function Component() {
+  function UnstableNestedComponent() {
+    return <div />;
+  }
+
+  return (
+    <div>
+      <UnstableNestedComponent />
+    </div>
+  );
+}
+```
+
+```jsx
+function SomeComponent({ footer: Footer }) {
+  return (
+    <div>
+      <Footer />
+    </div>
+  );
+}
+
+function Component() {
+  return (
+    <div>
+      <SomeComponent footer={() => <div />} />
+    </div>
+  );
+}
+```
+
+```jsx
+class Component extends React.Component {
+  render() {
+    function UnstableNestedComponent() {
+      return <div />;
+    }
+
+    return (
+      <div>
+        <UnstableNestedComponent />
+      </div>
+    );
+  }
+}
+```
+
+The following patterns are **not** considered warnings:
+
+```jsx
+function OutsideDefinedComponent(props) {
+  return <div />;
+}
+
+function Component() {
+  return (
+    <div>
+      <OutsideDefinedComponent />
+    </div>
+  );
+}
+```
+
+```jsx
+function Component() {
+  const MemoizedNestedComponent = React.useCallback(() => <div />, []);
+
+  return (
+    <div>
+      <MemoizedNestedComponent />
+    </div>
+  );
+}
+```
+
+```jsx
+function Component() {
+  return (
+    <SomeComponent footer={<div />} />
+  )
+}
+```
+
+By default component creation is allowed inside component props only if prop name starts with `render`. See `allowAsProps` option for disabling this limitation completely.
+
+```jsx
+function SomeComponent(props) {
+  return <div>{props.renderFooter()}</div>;
+}
+
+function Component() {
+  return (
+    <div>
+      <SomeComponent renderFooter={() => <div />} />
+    </div>
+  );
+}
+```
+
+## Rule Options
+
+```js
+...
+"react/no-unstable-nested-components": [
+  "off" | "warn" | "error",
+  { "allowAsProps": true | false }
+]
+...
+```
+
+You can allow component creation inside component props by setting `allowAsProps` option to true. When using this option make sure you are **calling** the props in the receiving component and not using them as elements.
+
+The following patterns are **not** considered warnings:
+
+```jsx
+function SomeComponent(props) {
+  return <div>{props.footer()}</div>;
+}
+
+function Component() {
+  return (
+    <div>
+      <SomeComponent footer={() => <div />} />
+    </div>
+  );
+}
+```
+
+## When Not To Use It
+
+If you are not interested in preventing bugs related to re-creation of the nested components or do not care about optimization of virtual DOM.
diff --git a/index.js b/index.js
index 8edb1177f1..f5d9872cc0 100644
--- a/index.js
+++ b/index.js
@@ -76,6 +76,7 @@ const allRules = {
   'no-unescaped-entities': require('./lib/rules/no-unescaped-entities'),
   'no-unknown-property': require('./lib/rules/no-unknown-property'),
   'no-unsafe': require('./lib/rules/no-unsafe'),
+  'no-unstable-nested-components': require('./lib/rules/no-unstable-nested-components'),
   'no-unused-prop-types': require('./lib/rules/no-unused-prop-types'),
   'no-unused-state': require('./lib/rules/no-unused-state'),
   'no-will-update-set-state': require('./lib/rules/no-will-update-set-state'),
diff --git a/lib/rules/no-unstable-nested-components.js b/lib/rules/no-unstable-nested-components.js
new file mode 100644
index 0000000000..56db0e06e4
--- /dev/null
+++ b/lib/rules/no-unstable-nested-components.js
@@ -0,0 +1,468 @@
+/**
+ * @fileoverview Prevent creating unstable components inside components
+ * @author Ari Perkkiö
+ */
+
+'use strict';
+
+const Components = require('../util/Components');
+const docsUrl = require('../util/docsUrl');
+
+// ------------------------------------------------------------------------------
+// Constants
+// ------------------------------------------------------------------------------
+
+const ERROR_MESSAGE_WITHOUT_NAME = 'Declare this component outside parent component or memoize it.';
+const COMPONENT_AS_PROPS_INFO = ' If you want to allow component creation in props, set allowAsProps option to true.';
+const HOOK_REGEXP = /^use[A-Z0-9].*$/;
+
+// ------------------------------------------------------------------------------
+// Helpers
+// ------------------------------------------------------------------------------
+
+/**
+ * Generate error message with given parent component name
+ * @param {String} parentName Name of the parent component
+ * @returns {String} Error message with parent component name
+ */
+function generateErrorMessageWithParentName(parentName) {
+  return `Declare this component outside parent component "${parentName}" or memoize it.`;
+}
+
+/**
+ * Check whether given text starts with `render`. Comparison is case-sensitive.
+ * @param {String} text Text to validate
+ * @returns {Boolean}
+ */
+function startsWithRender(text) {
+  return (text || '').startsWith('render');
+}
+
+/**
+ * Get closest parent matching given matcher
+ * @param {ASTNode} node The AST node
+ * @param {Function} matcher Method used to match the parent
+ * @returns {ASTNode} The matching parent node, if any
+ */
+function getClosestMatchingParent(node, matcher) {
+  if (!node || !node.parent || node.parent.type === 'Program') {
+    return;
+  }
+
+  if (matcher(node.parent)) {
+    return node.parent;
+  }
+
+  return getClosestMatchingParent(node.parent, matcher);
+}
+
+/**
+ * Matcher used to check whether given node is a `createElement` call
+ * @param {ASTNode} node The AST node
+ * @returns {Boolean} True if node is a `createElement` call, false if not
+ */
+function isCreateElementMatcher(node) {
+  return (
+    node
+    && node.type === 'CallExpression'
+    && node.callee
+    && node.callee.property
+    && node.callee.property.name === 'createElement'
+  );
+}
+
+/**
+ * Matcher used to check whether given node is a `ObjectExpression`
+ * @param {ASTNode} node The AST node
+ * @returns {Boolean} True if node is a `ObjectExpression`, false if not
+ */
+function isObjectExpressionMatcher(node) {
+  return node && node.type === 'ObjectExpression';
+}
+
+/**
+ * Matcher used to check whether given node is a `JSXExpressionContainer`
+ * @param {ASTNode} node The AST node
+ * @returns {Boolean} True if node is a `JSXExpressionContainer`, false if not
+ */
+function isJSXExpressionContainerMatcher(node) {
+  return node && node.type === 'JSXExpressionContainer';
+}
+
+/**
+ * Matcher used to check whether given node is a `JSXAttribute` of `JSXExpressionContainer`
+ * @param {ASTNode} node The AST node
+ * @returns {Boolean} True if node is a `JSXAttribute` of `JSXExpressionContainer`, false if not
+ */
+function isJSXAttributeOfExpressionContainerMatcher(node) {
+  return (
+    node
+    && node.type === 'JSXAttribute'
+    && node.value
+    && node.value.type === 'JSXExpressionContainer'
+  );
+}
+
+/**
+ * Matcher used to check whether given node is a `CallExpression`
+ * @param {ASTNode} node The AST node
+ * @returns {Boolean} True if node is a `CallExpression`, false if not
+ */
+function isCallExpressionMatcher(node) {
+  return node && node.type === 'CallExpression';
+}
+
+/**
+ * Check whether given node or its parent is directly inside `map` call
+ * ```jsx
+ * {items.map(item => <li />)}
+ * ```
+ * @param {ASTNode} node The AST node
+ * @returns {Boolean} True if node is directly inside `map` call, false if not
+ */
+function isMapCall(node) {
+  return (
+    node
+    && node.callee
+    && node.callee.property
+    && node.callee.property.name === 'map'
+  );
+}
+
+/**
+ * Check whether given node is `ReturnStatement` of a React hook
+ * @param {ASTNode} node The AST node
+ * @returns {Boolean} True if node is a `ReturnStatement` of a React hook, false if not
+ */
+function isReturnStatementOfHook(node) {
+  if (
+    !node
+    || !node.parent
+    || node.parent.type !== 'ReturnStatement'
+  ) {
+    return false;
+  }
+
+  const callExpression = getClosestMatchingParent(node, isCallExpressionMatcher);
+  return (
+    callExpression
+    && callExpression.callee
+    && HOOK_REGEXP.test(callExpression.callee.name)
+  );
+}
+
+/**
+ * Check whether given node is declared inside a render prop
+ * ```jsx
+ * <Component renderFooter={() => <div />} />
+ * <Component>{() => <div />}</Component>
+ * ```
+ * @param {ASTNode} node The AST node
+ * @returns {Boolean} True if component is declared inside a render prop, false if not
+ */
+function isComponentInRenderProp(node) {
+  if (
+    node
+    && node.parent
+    && node.parent.type === 'Property'
+    && node.parent.key
+    && startsWithRender(node.parent.key.name)
+  ) {
+    return true;
+  }
+
+  // Check whether component is a render prop used as direct children, e.g. <Component>{() => <div />}</Component>
+  if (
+    node
+    && node.parent
+    && node.parent.type === 'JSXExpressionContainer'
+    && node.parent.parent
+    && node.parent.parent.type === 'JSXElement'
+  ) {
+    return true;
+  }
+
+  const jsxExpressionContainer = getClosestMatchingParent(node, isJSXExpressionContainerMatcher);
+
+  // Check whether prop name indicates accepted patterns
+  if (
+    jsxExpressionContainer
+    && jsxExpressionContainer.parent
+    && jsxExpressionContainer.parent.type === 'JSXAttribute'
+    && jsxExpressionContainer.parent.name
+    && jsxExpressionContainer.parent.name.type === 'JSXIdentifier'
+  ) {
+    const propName = jsxExpressionContainer.parent.name.name;
+
+    // Starts with render, e.g. <Component renderFooter={() => <div />} />
+    if (startsWithRender(propName)) {
+      return true;
+    }
+
+    // Uses children prop explicitly, e.g. <Component children={() => <div />} />
+    if (propName === 'children') {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+/**
+ * Check whether given node is declared directly inside a render property
+ * ```jsx
+ * const rows = { render: () => <div /> }
+ * <Component rows={ [{ render: () => <div /> }] } />
+ *  ```
+ * @param {ASTNode} node The AST node
+ * @returns {Boolean} True if component is declared inside a render property, false if not
+ */
+function isDirectValueOfRenderProperty(node) {
+  return (
+    node
+    && node.parent
+    && node.parent.type === 'Property'
+    && node.parent.key
+    && node.parent.key.type === 'Identifier'
+    && startsWithRender(node.parent.key.name)
+  );
+}
+
+/**
+ * Resolve the component name of given node
+ * @param {ASTNode} node The AST node of the component
+ * @returns {String} Name of the component, if any
+ */
+function resolveComponentName(node) {
+  const parentName = node.id && node.id.name;
+  if (parentName) return parentName;
+
+  return (
+    node.type === 'ArrowFunctionExpression'
+    && node.parent
+    && node.parent.id
+    && node.parent.id.name
+  );
+}
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = {
+  meta: {
+    docs: {
+      description: 'Prevent creating unstable components inside components',
+      category: 'Possible Errors',
+      recommended: false,
+      url: docsUrl('no-unstable-nested-components')
+    },
+    schema: [{
+      type: 'object',
+      properties: {
+        customValidators: {
+          type: 'array',
+          items: {
+            type: 'string'
+          }
+        },
+        allowAsProps: {
+          type: 'boolean'
+        }
+      },
+      additionalProperties: false
+    }]
+  },
+
+  create: Components.detect((context, components, utils) => {
+    const allowAsProps = context.options.some((option) => option && option.allowAsProps);
+
+    /**
+     * Check whether given node is declared inside class component's render block
+     * ```jsx
+     * class Component extends React.Component {
+     *   render() {
+     *     class NestedClassComponent extends React.Component {
+     * ...
+     * ```
+     * @param {ASTNode} node The AST node being checked
+     * @returns {Boolean} True if node is inside class component's render block, false if not
+     */
+    function isInsideRenderMethod(node) {
+      const parentComponent = utils.getParentComponent();
+
+      if (!parentComponent || parentComponent.type !== 'ClassDeclaration') {
+        return false;
+      }
+
+      return (
+        node
+        && node.parent
+        && node.parent.type === 'MethodDefinition'
+        && node.parent.key
+        && node.parent.key.name === 'render'
+      );
+    }
+
+    /**
+     * Check whether given node is a function component declared inside class component.
+     * Util's component detection fails to detect function components inside class components.
+     * ```jsx
+     * class Component extends React.Component {
+     *  render() {
+     *    const NestedComponent = () => <div />;
+     * ...
+     * ```
+     * @param {ASTNode} node The AST node being checked
+     * @returns {Boolean} True if given node a function component declared inside class component, false if not
+     */
+    function isFunctionComponentInsideClassComponent(node) {
+      const parentComponent = utils.getParentComponent();
+      const parentStatelessComponent = utils.getParentStatelessComponent();
+
+      return (
+        parentComponent
+        && parentStatelessComponent
+        && parentComponent.type === 'ClassDeclaration'
+        && utils.getStatelessComponent(parentStatelessComponent)
+        && utils.isReturningJSX(node)
+      );
+    }
+
+    /**
+     * Check whether given node is declared inside `createElement` call's props
+     * ```js
+     * React.createElement(Component, {
+     *   footer: () => React.createElement("div", null)
+     * })
+     * ```
+     * @param {ASTNode} node The AST node
+     * @returns {Boolean} True if node is declare inside `createElement` call's props, false if not
+     */
+    function isComponentInsideCreateElementsProp(node) {
+      if (!components.get(node)) {
+        return false;
+      }
+
+      const createElementParent = getClosestMatchingParent(node, isCreateElementMatcher);
+
+      return (
+        createElementParent
+        && createElementParent.arguments
+        && createElementParent.arguments[1] === getClosestMatchingParent(node, isObjectExpressionMatcher)
+      );
+    }
+
+    /**
+     * Check whether given node is declared inside a component prop.
+     * ```jsx
+     * <Component footer={() => <div />} />
+     * ```
+     * @param {ASTNode} node The AST node being checked
+     * @returns {Boolean} True if node is a component declared inside prop, false if not
+     */
+    function isComponentInProp(node) {
+      const jsxAttribute = getClosestMatchingParent(node, isJSXAttributeOfExpressionContainerMatcher);
+
+      if (!jsxAttribute) {
+        return isComponentInsideCreateElementsProp(node);
+      }
+
+      return utils.isReturningJSX(node);
+    }
+
+    /**
+     * Check whether given node is a stateless component returning non-JSX
+     * ```jsx
+     * {{ a: () => null }}
+     * ```
+     * @param {ASTNode} node The AST node being checked
+     * @returns {Boolean} True if node is a stateless component returning non-JSX, false if not
+     */
+    function isStatelessComponentReturningNull(node) {
+      const component = utils.getStatelessComponent(node);
+
+      return component && !utils.isReturningJSX(component);
+    }
+
+    /**
+     * Check whether given node is a unstable nested component
+     * @param {ASTNode} node The AST node being checked
+     */
+    function validate(node) {
+      if (!node || !node.parent) {
+        return;
+      }
+
+      const isDeclaredInsideProps = isComponentInProp(node);
+
+      if (
+        !components.get(node)
+        && !isFunctionComponentInsideClassComponent(node)
+        && !isDeclaredInsideProps) {
+        return;
+      }
+
+      if (
+        // Support allowAsProps option
+        (isDeclaredInsideProps && (allowAsProps || isComponentInRenderProp(node)))
+
+        // Prevent reporting components created inside Array.map calls
+        || isMapCall(node)
+        || isMapCall(node.parent)
+
+        // Do not mark components declared inside hooks (or falsly '() => null' clean-up methods)
+        || isReturnStatementOfHook(node)
+
+        // Do not mark objects containing render methods
+        || isDirectValueOfRenderProperty(node)
+
+        // Prevent reporting nested class components twice
+        || isInsideRenderMethod(node)
+
+        // Prevent falsely reporting deteceted "components" which do not return JSX
+        || isStatelessComponentReturningNull(node)
+      ) {
+        return;
+      }
+
+      // Get the closest parent component
+      const parentComponent = getClosestMatchingParent(
+        node,
+        (nodeToMatch) => components.get(nodeToMatch)
+      );
+
+      if (parentComponent) {
+        const parentName = resolveComponentName(parentComponent);
+
+        // Exclude lowercase parents, e.g. function createTestComponent()
+        // React-dom prevents creating lowercase components
+        if (parentName && parentName[0] === parentName[0].toLowerCase()) {
+          return;
+        }
+
+        let message = parentName
+          ? generateErrorMessageWithParentName(parentName)
+          : ERROR_MESSAGE_WITHOUT_NAME;
+
+        // Add information about allowAsProps option when component is declared inside prop
+        if (isDeclaredInsideProps && !allowAsProps) {
+          message += COMPONENT_AS_PROPS_INFO;
+        }
+
+        context.report({node, message});
+      }
+    }
+
+    // --------------------------------------------------------------------------
+    // Public
+    // --------------------------------------------------------------------------
+
+    return {
+      FunctionDeclaration(node) { validate(node); },
+      ArrowFunctionExpression(node) { validate(node); },
+      FunctionExpression(node) { validate(node); },
+      ClassDeclaration(node) { validate(node); }
+    };
+  })
+};


Test patch:
diff --git a/tests/lib/rules/no-unstable-nested-components.js b/tests/lib/rules/no-unstable-nested-components.js
new file mode 100644
index 0000000000..d6df486d86
--- /dev/null
+++ b/tests/lib/rules/no-unstable-nested-components.js
@@ -0,0 +1,1051 @@
+/**
+ * @fileoverview Prevent creating unstable components inside components
+ * @author Ari Perkkiö
+ */
+
+'use strict';
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const RuleTester = require('eslint').RuleTester;
+const rule = require('../../../lib/rules/no-unstable-nested-components');
+
+const parserOptions = {
+  ecmaVersion: 2018,
+  sourceType: 'module',
+  ecmaFeatures: {
+    jsx: true
+  }
+};
+
+const ERROR_MESSAGE = 'Declare this component outside parent component "ParentComponent" or memoize it.';
+const ERROR_MESSAGE_WITHOUT_NAME = 'Declare this component outside parent component or memoize it.';
+const ERROR_MESSAGE_COMPONENT_AS_PROPS = `${ERROR_MESSAGE} If you want to allow component creation in props, set allowAsProps option to true.`;
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester({parserOptions});
+ruleTester.run('no-unstable-nested-components', rule, {
+  valid: [
+    {
+      code: `
+      function ParentComponent() {
+        return (
+          <div>
+            <OutsideDefinedFunctionComponent />
+          </div>
+        );
+      }
+      `
+    },
+    {
+      code: `
+      function ParentComponent() {
+        return React.createElement(
+          "div",
+          null,
+          React.createElement(OutsideDefinedFunctionComponent, null)
+        );
+      }
+      `
+    },
+    {
+      code: `
+      function ParentComponent() {
+        return (
+          <SomeComponent
+            footer={<OutsideDefinedComponent />}
+            header={<div />}
+            />
+        );
+      }
+      `
+    },
+    {
+      code: `
+      function ParentComponent() {
+        return React.createElement(SomeComponent, {
+          footer: React.createElement(OutsideDefinedComponent, null),
+          header: React.createElement("div", null)
+        });
+      }
+      `
+    },
+    {
+      code: `
+      function ParentComponent() {
+        const MemoizedNestedComponent = React.useCallback(() => <div />, []);
+
+        return (
+          <div>
+            <MemoizedNestedComponent />
+          </div>
+        );
+      }
+      `
+    },
+    {
+      code: `
+      function ParentComponent() {
+        const MemoizedNestedComponent = React.useCallback(
+          () => React.createElement("div", null),
+          []
+        );
+
+        return React.createElement(
+          "div",
+          null,
+          React.createElement(MemoizedNestedComponent, null)
+        );
+      }
+      `
+    },
+    {
+      code: `
+      function ParentComponent() {
+        const MemoizedNestedFunctionComponent = React.useCallback(
+          function () {
+            return <div />;
+          },
+          []
+        );
+
+        return (
+          <div>
+            <MemoizedNestedFunctionComponent />
+          </div>
+        );
+      }
+      `
+    },
+    {
+      code: `
+      function ParentComponent() {
+        const MemoizedNestedFunctionComponent = React.useCallback(
+          function () {
+            return React.createElement("div", null);
+          },
+          []
+        );
+
+        return React.createElement(
+          "div",
+          null,
+          React.createElement(MemoizedNestedFunctionComponent, null)
+        );
+      }
+      `
+    },
+    {
+      code: `
+      function ParentComponent(props) {
+        // Should not interfere handler declarations
+        function onClick(event) {
+          props.onClick(event.target.value);
+        }
+
+        const onKeyPress = () => null;
+
+        function getOnHover() {
+          return function onHover(event) {
+            props.onHover(event.target);
+          }
+        }
+
+        return (
+          <div>
+            <button
+              onClick={onClick}
+              onKeyPress={onKeyPress}
+              onHover={getOnHover()}
+
+              // These should not be considered as components
+              maybeComponentOrHandlerNull={() => null}
+              maybeComponentOrHandlerUndefined={() => undefined}
+              maybeComponentOrHandlerBlank={() => ''}
+              maybeComponentOrHandlerString={() => 'hello-world'}
+              maybeComponentOrHandlerNumber={() => 42}
+              maybeComponentOrHandlerArray={() => []}
+              maybeComponentOrHandlerObject={() => {}} />
+          </div>
+        );
+      }
+      `
+    },
+    {
+      code: `
+      function ParentComponent() {
+        function getComponent() {
+          return <div />;
+        }
+
+        return (
+          <div>
+            {getComponent()}
+          </div>
+        );
+      }
+      `
+    },
+    {
+      code: `
+      function ParentComponent() {
+        function getComponent() {
+          return React.createElement("div", null);
+        }
+
+        return React.createElement("div", null, getComponent());
+      }
+      `
+    },
+    {
+      code: `
+      function ParentComponent() {
+          return (
+            <RenderPropComponent>
+              {() => <div />}
+            </RenderPropComponent>
+          );
+      }
+      `
+    },
+    {
+      code: `
+      function ParentComponent() {
+          return (
+            <RenderPropComponent children={() => <div />} />
+          );
+      }
+      `
+    },
+    {
+      code: `
+      function ParentComponent() {
+        return (
+          <ComplexRenderPropComponent
+            listRenderer={data.map((items, index) => (
+              <ul>
+                {items[index].map((item) =>
+                  <li>
+                    {item}
+                  </li>
+                )}
+              </ul>
+            ))
+            }
+          />
+        );
+      }
+      `
+    },
+    {
+      code: `
+      function ParentComponent() {
+        return React.createElement(
+            RenderPropComponent,
+            null,
+            () => React.createElement("div", null)
+        );
+      }
+      `
+    },
+    {
+      code: `
+      function ParentComponent(props) {
+        return (
+          <ul>
+            {props.items.map(item => (
+              <li key={item.id}>
+                {item.name}
+              </li>
+            ))}
+          </ul>
+        );
+      }
+      `
+    },
+    {
+      code: `
+      function ParentComponent(props) {
+        return (
+          <List items={props.items.map(item => {
+            return (
+              <li key={item.id}>
+                {item.name}
+              </li>
+            );
+          })}
+          />
+        );
+      }
+      `
+    },
+    {
+      code: `
+      function ParentComponent(props) {
+        return React.createElement(
+          "ul",
+          null,
+          props.items.map(() =>
+            React.createElement(
+              "li",
+              { key: item.id },
+              item.name
+            )
+          )
+        )
+      }
+      `
+    },
+    {
+      code: `
+      function ParentComponent(props) {
+        return (
+          <ul>
+            {props.items.map(function Item(item) {
+              return (
+                <li key={item.id}>
+                  {item.name}
+                </li>
+              );
+            })}
+          </ul>
+        );
+      }
+      `
+    },
+    {
+      code: `
+      function ParentComponent(props) {
+        return React.createElement(
+          "ul",
+          null,
+          props.items.map(function Item() {
+            return React.createElement(
+              "li",
+              { key: item.id },
+              item.name
+            );
+          })
+        );
+      }
+      `
+    },
+    {
+      code: `
+      function createTestComponent(props) {
+        return (
+          <div />
+        );
+      }
+      `
+    },
+    {
+      code: `
+      function createTestComponent(props) {
+        return React.createElement("div", null);
+      }
+      `
+    },
+    {
+      code: `
+      function ParentComponent() {
+        return (
+          <ComponentWithProps footer={() => <div />} />
+        );
+      }
+      `,
+      options: [{
+        allowAsProps: true
+      }]
+    },
+    {
+      code: `
+      function ParentComponent() {
+        return React.createElement(ComponentWithProps, {
+          footer: () => React.createElement("div", null)
+        });
+      }
+      `,
+      options: [{
+        allowAsProps: true
+      }]
+    },
+    {
+      code: `
+      function ParentComponent() {
+        return (
+          <SomeComponent item={{ children: () => <div /> }} />
+        )
+      }
+      `,
+      options: [{
+        allowAsProps: true
+      }]
+    },
+    {
+      code: `
+      function ParentComponent() {
+        return (
+          <ComponentForProps renderFooter={() => <div />} />
+        );
+      }
+      `
+    },
+    {
+      code: `
+      function ParentComponent() {
+        return React.createElement(ComponentForProps, {
+          renderFooter: () => React.createElement("div", null)
+        });
+      }
+      `
+    },
+    {
+      code: `
+      function ParentComponent() {
+        useEffect(() => {
+          return () => null;
+        });
+
+        return <div />;
+      }
+      `
+    },
+    {
+      code: `
+      function ParentComponent() {
+        return (
+          <SomeComponent renderers={{ Header: () => <div /> }} />
+        )
+      }
+      `
+    },
+    {
+      code: `
+      function ParentComponent() {
+        return (
+          <SomeComponent renderMenu={() => (
+            <RenderPropComponent>
+              {items.map(item => (
+                <li key={item}>{item}</li>
+              ))}
+            </RenderPropComponent>
+          )} />
+        )
+      }
+      `
+    },
+    {
+      code: `
+      const ParentComponent = () => (
+        <SomeComponent
+          components={[
+            <ul>
+              {list.map(item => (
+                <li key={item}>{item}</li>
+              ))}
+            </ul>,
+          ]}
+        />
+      );
+     `
+    },
+    {
+      code: `
+      function ParentComponent() {
+        const rows = [
+          {
+            name: 'A',
+            render: (props) => <Row {...props} />
+          },
+        ];
+
+        return <Table rows={rows} />;
+      }
+      `
+    },
+    {
+      code: `
+      function ParentComponent() {
+        return <SomeComponent renderers={{ notComponent: () => null }} />;
+      }
+      `
+    },
+    {
+      code: `
+      const ParentComponent = createReactClass({
+        displayName: "ParentComponent",
+        statics: {
+          getSnapshotBeforeUpdate: function () {
+            return null;
+          },
+        },
+        render() {
+          return <div />;
+        },
+      });
+      `
+    }
+    /* TODO These minor cases are currently falsely marked due to component detection
+    {
+      code: `
+      function ParentComponent() {
+        const _renderHeader = () => <div />;
+        return <div>{_renderHeader()}</div>;
+      }
+      `
+    },
+    {
+      // https://github.com/emotion-js/emotion/blob/a89d4257b0246a1640a99f77838e5edad4ec4386/packages/jest/test/react-enzyme.test.js#L26-L28
+      code: `
+      const testCases = {
+        basic: {
+          render() {
+            const Component = () => <div />;
+            return <div />;
+          }
+        }
+      }
+      `
+    },
+    */
+  ],
+
+  invalid: [
+    {
+      code: `
+      function ParentComponent() {
+        function UnstableNestedFunctionComponent() {
+          return <div />;
+        }
+
+        return (
+          <div>
+            <UnstableNestedFunctionComponent />
+          </div>
+        );
+      }
+      `,
+      errors: [{message: ERROR_MESSAGE}]
+    },
+    {
+      code: `
+      function ParentComponent() {
+        function UnstableNestedFunctionComponent() {
+          return React.createElement("div", null);
+        }
+
+        return React.createElement(
+          "div",
+          null,
+          React.createElement(UnstableNestedFunctionComponent, null)
+        );
+      }
+      `,
+      errors: [{message: ERROR_MESSAGE}]
+    },
+    {
+      code: `
+      function ParentComponent() {
+        const UnstableNestedVariableComponent = () => {
+          return <div />;
+        }
+
+        return (
+          <div>
+            <UnstableNestedVariableComponent />
+          </div>
+        );
+      }
+      `,
+      errors: [{message: ERROR_MESSAGE}]
+    },
+    {
+      code: `
+      function ParentComponent() {
+        const UnstableNestedVariableComponent = () => {
+          return React.createElement("div", null);
+        }
+
+        return React.createElement(
+          "div",
+          null,
+          React.createElement(UnstableNestedVariableComponent, null)
+        );
+      }
+      `,
+      errors: [{message: ERROR_MESSAGE}]
+    },
+    {
+      code: `
+      const ParentComponent = () => {
+        function UnstableNestedFunctionComponent() {
+          return <div />;
+        }
+
+        return (
+          <div>
+            <UnstableNestedFunctionComponent />
+          </div>
+        );
+      }
+      `,
+      errors: [{message: ERROR_MESSAGE}]
+    },
+    {
+      code: `
+      const ParentComponent = () => {
+        function UnstableNestedFunctionComponent() {
+          return React.createElement("div", null);
+        }
+
+        return React.createElement(
+          "div",
+          null,
+          React.createElement(UnstableNestedFunctionComponent, null)
+        );
+      }
+      `,
+      errors: [{message: ERROR_MESSAGE}]
+    },
+    {
+      code: `
+      export default () => {
+        function UnstableNestedFunctionComponent() {
+          return <div />;
+        }
+
+        return (
+          <div>
+            <UnstableNestedFunctionComponent />
+          </div>
+        );
+      }
+      `,
+      errors: [{message: ERROR_MESSAGE_WITHOUT_NAME}]
+    },
+    {
+      code: `
+      export default () => {
+        function UnstableNestedFunctionComponent() {
+          return React.createElement("div", null);
+        }
+
+        return React.createElement(
+          "div",
+          null,
+          React.createElement(UnstableNestedFunctionComponent, null)
+        );
+      };
+      `,
+      errors: [{message: ERROR_MESSAGE_WITHOUT_NAME}]
+    },
+    {
+      code: `
+      const ParentComponent = () => {
+        const UnstableNestedVariableComponent = () => {
+          return <div />;
+        }
+
+        return (
+          <div>
+            <UnstableNestedVariableComponent />
+          </div>
+        );
+      }
+      `,
+      errors: [{message: ERROR_MESSAGE}]
+    },
+    {
+      code: `
+      const ParentComponent = () => {
+        const UnstableNestedVariableComponent = () => {
+          return React.createElement("div", null);
+        }
+
+        return React.createElement(
+          "div",
+          null,
+          React.createElement(UnstableNestedVariableComponent, null)
+        );
+      }
+      `,
+      errors: [{message: ERROR_MESSAGE}]
+    },
+    {
+      code: `
+      function ParentComponent() {
+        class UnstableNestedClassComponent extends React.Component {
+          render() {
+            return <div />;
+          }
+        };
+
+        return (
+          <div>
+            <UnstableNestedClassComponent />
+          </div>
+        );
+      }
+      `,
+      errors: [{message: ERROR_MESSAGE}]
+    },
+    {
+      code: `
+      function ParentComponent() {
+        class UnstableNestedClassComponent extends React.Component {
+          render() {
+            return React.createElement("div", null);
+          }
+        }
+
+        return React.createElement(
+          "div",
+          null,
+          React.createElement(UnstableNestedClassComponent, null)
+        );
+      }
+      `,
+      errors: [{message: ERROR_MESSAGE}]
+    },
+    {
+      code: `
+      class ParentComponent extends React.Component {
+        render() {
+          class UnstableNestedClassComponent extends React.Component {
+            render() {
+              return <div />;
+            }
+          };
+
+          return (
+            <div>
+              <UnstableNestedClassComponent />
+            </div>
+          );
+        }
+      }
+      `,
+      errors: [{message: ERROR_MESSAGE}]
+    },
+    {
+      code: `
+      class ParentComponent extends React.Component {
+        render() {
+          class UnstableNestedClassComponent extends React.Component {
+            render() {
+              return React.createElement("div", null);
+            }
+          }
+
+          return React.createElement(
+            "div",
+            null,
+            React.createElement(UnstableNestedClassComponent, null)
+          );
+        }
+      }
+      `,
+      errors: [{message: ERROR_MESSAGE}]
+    },
+    {
+      code: `
+      class ParentComponent extends React.Component {
+        render() {
+          function UnstableNestedFunctionComponent() {
+            return <div />;
+          }
+
+          return (
+            <div>
+              <UnstableNestedFunctionComponent />
+            </div>
+          );
+        }
+      }
+      `,
+      errors: [{message: ERROR_MESSAGE}]
+    },
+    {
+      code: `
+      class ParentComponent extends React.Component {
+        render() {
+          function UnstableNestedClassComponent() {
+            return React.createElement("div", null);
+          }
+
+          return React.createElement(
+            "div",
+            null,
+            React.createElement(UnstableNestedClassComponent, null)
+          );
+        }
+      }
+      `,
+      errors: [{message: ERROR_MESSAGE}]
+    },
+    {
+      code: `
+      class ParentComponent extends React.Component {
+        render() {
+          const UnstableNestedVariableComponent = () => {
+            return <div />;
+          }
+
+          return (
+            <div>
+              <UnstableNestedVariableComponent />
+            </div>
+          );
+        }
+      }
+      `,
+      errors: [{message: ERROR_MESSAGE}]
+    },
+    {
+      code: `
+      class ParentComponent extends React.Component {
+        render() {
+          const UnstableNestedClassComponent = () => {
+            return React.createElement("div", null);
+          }
+
+          return React.createElement(
+            "div",
+            null,
+            React.createElement(UnstableNestedClassComponent, null)
+          );
+        }
+      }
+      `,
+      errors: [{message: ERROR_MESSAGE}]
+    },
+    {
+      code: `
+      function ParentComponent() {
+        function getComponent() {
+          function NestedUnstableFunctionComponent() {
+            return <div />;
+          };
+
+          return <NestedUnstableFunctionComponent />;
+        }
+
+        return (
+          <div>
+            {getComponent()}
+          </div>
+        );
+      }
+      `,
+      errors: [{message: ERROR_MESSAGE}]
+    },
+    {
+      code: `
+      function ParentComponent() {
+        function getComponent() {
+          function NestedUnstableFunctionComponent() {
+            return React.createElement("div", null);
+          }
+
+          return React.createElement(NestedUnstableFunctionComponent, null);
+        }
+
+        return React.createElement("div", null, getComponent());
+      }
+      `,
+      errors: [{message: ERROR_MESSAGE}]
+    },
+    {
+      code: `
+      function ComponentWithProps(props) {
+        return <div />;
+      }
+
+      function ParentComponent() {
+        return (
+          <ComponentWithProps
+            footer={
+              function SomeFooter() {
+                return <div />;
+              }
+            } />
+        );
+      }
+      `,
+      errors: [{message: ERROR_MESSAGE_COMPONENT_AS_PROPS}]
+    },
+    {
+      code: `
+      function ComponentWithProps(props) {
+        return React.createElement("div", null);
+      }
+
+      function ParentComponent() {
+        return React.createElement(ComponentWithProps, {
+          footer: function SomeFooter() {
+            return React.createElement("div", null);
+          }
+        });
+      }
+      `,
+      errors: [{message: ERROR_MESSAGE_COMPONENT_AS_PROPS}]
+    },
+    {
+      code: `
+      function ComponentWithProps(props) {
+        return <div />;
+      }
+
+      function ParentComponent() {
+          return (
+            <ComponentWithProps footer={() => <div />} />
+          );
+      }
+      `,
+      errors: [{message: ERROR_MESSAGE_COMPONENT_AS_PROPS}]
+    },
+    {
+      code: `
+      function ComponentWithProps(props) {
+        return React.createElement("div", null);
+      }
+
+      function ParentComponent() {
+        return React.createElement(ComponentWithProps, {
+          footer: () => React.createElement("div", null)
+        });
+      }
+      `,
+      errors: [{message: ERROR_MESSAGE_COMPONENT_AS_PROPS}]
+    },
+    {
+      code: `
+      function ParentComponent() {
+          return (
+            <RenderPropComponent>
+              {() => {
+                function UnstableNestedComponent() {
+                  return <div />;
+                }
+
+                return (
+                  <div>
+                    <UnstableNestedComponent />
+                  </div>
+                );
+              }}
+            </RenderPropComponent>
+          );
+      }
+      `,
+      errors: [{message: ERROR_MESSAGE}]
+    },
+    {
+      code: `
+      function RenderPropComponent(props) {
+        return props.render({});
+      }
+
+      function ParentComponent() {
+        return React.createElement(
+          RenderPropComponent,
+          null,
+          () => {
+            function UnstableNestedComponent() {
+              return React.createElement("div", null);
+            }
+
+            return React.createElement(
+              "div",
+              null,
+              React.createElement(UnstableNestedComponent, null)
+            );
+          }
+        );
+      }
+      `,
+      errors: [{message: ERROR_MESSAGE}]
+    },
+    {
+      code: `
+      function ComponentForProps(props) {
+        return <div />;
+      }
+
+      function ParentComponent() {
+        return (
+          <ComponentForProps notPrefixedWithRender={() => <div />} />
+        );
+      }
+      `,
+      errors: [{message: ERROR_MESSAGE_COMPONENT_AS_PROPS}]
+    },
+    {
+      code: `
+      function ComponentForProps(props) {
+        return React.createElement("div", null);
+      }
+
+      function ParentComponent() {
+        return React.createElement(ComponentForProps, {
+          notPrefixedWithRender: () => React.createElement("div", null)
+        });
+      }
+      `,
+      errors: [{message: ERROR_MESSAGE_COMPONENT_AS_PROPS}]
+    },
+    {
+      code: `
+      function ParentComponent() {
+        return (
+          <ComponentForProps someMap={{ Header: () => <div /> }} />
+        );
+      }
+      `,
+      errors: [{message: ERROR_MESSAGE_COMPONENT_AS_PROPS}]
+    },
+    {
+      code: `
+      function ParentComponent() {
+        const rows = [
+          {
+            name: 'A',
+            notPrefixedWithRender: (props) => <Row {...props} />
+          },
+        ];
+
+        return <Table rows={rows} />;
+      }
+      `,
+      errors: [{message: ERROR_MESSAGE}]
+    },
+    {
+      code: `
+      class ParentComponent extends React.Component {
+        render() {
+          const List = (props) => {
+            const items = props.items
+              .map((item) => (
+                <li key={item.key}>
+                  <span>{item.name}</span>
+                </li>
+              ));
+
+            return <ul>{items}</ul>;
+          };
+
+          return <List {...this.props} />;
+        }
+      }
+      `,
+      // Only a single error should be shown. This can get easily marked twice.
+      errors: [{message: ERROR_MESSAGE}]
+    }
+  ]
+});