no-clone-element
Disallows 'cloneElement'.
Full Name in eslint-plugin-react-x
react-x/no-clone-elementFull Name in @eslint-react/eslint-plugin
@eslint-react/no-clone-elementPresets
x
recommended
recommended-typescript
recommended-type-checked
strict
strict-typescript
strict-type-checked
Rule Details
Using cloneElement to inject props into children is a form of React child introspection. It creates fragile coupling between parent and child components:
- Breaks when children are wrapped in HOCs,
forwardRef, ormemo. - Prevents React from optimizing rendering.
- Creates implicit contracts that types can't enforce.
- Makes composition unpredictable with fragments, portals, and other React features.
Prefer these alternatives:
- Compound components with context
- Render props or slot props
- Data-driven APIs (array of config objects)
- CSS-based solutions (grid, flexbox,
:nth-child, etc.)
Try the alternatives to cloneElement instead.
Examples
Augmenting an element's props dynamically
Using cloneElement to inject props into a child element hides the data flow and breaks component encapsulation. Prefer passing props explicitly through render props or composition patterns.
// 🔴 Problem: Using cloneElement to dynamically inject props into a child element
import { cloneElement } from "react";
const clonedElement = cloneElement(
<Row title="Cabbage">Hello</Row>,
{ isHighlighted: true },
"Goodbye",
);
console.log(clonedElement); // <Row title="Cabbage" isHighlighted={true}>Goodbye</Row>Wrapping children with injected props
Using cloneElement inside Children.map hides which props a child receives and breaks encapsulation. Prefer passing data explicitly via render props or by restructuring your component API.
// 🔴 Problem: Using cloneElement to inject props into each child
import { Children, cloneElement, useState } from "react";
function List({ children }) {
const [selectedIndex, setSelectedIndex] = useState(0);
return (
<div className="List">
{Children.map(children, (child, index) =>
cloneElement(child, {
isHighlighted: index === selectedIndex,
}),
)}
<button onClick={() => setSelectedIndex((i) => (i + 1) % Children.count(children))}>
Next
</button>
</div>
);
}// 🟢 Recommended: Pass isHighlighted explicitly via render props
import { useState } from "react";
function List({ items, renderItem }) {
const [selectedIndex, setSelectedIndex] = useState(0);
return (
<div className="List">
{items.map((item, index) => {
const isHighlighted = index === selectedIndex;
return renderItem(item, isHighlighted);
})}
<button onClick={() => setSelectedIndex((i) => (i + 1) % items.length)}>
Next
</button>
</div>
);
}
function App() {
return (
<List
items={products}
renderItem={(product, isHighlighted) => (
<Row key={product.id} title={product.title} isHighlighted={isHighlighted} />
)}
/>
);
}Using component composition instead of cloneElement
Instead of injecting props into children with cloneElement, compose components so that the parent controls the layout and passes props explicitly.
// 🟢 Recommended: Use component composition to wrap and configure items
interface RowProps {
title: string;
isHighlighted?: boolean;
children: React.ReactNode;
}
function Row({ title, isHighlighted, children }: RowProps) {
return (
<div className={isHighlighted ? "Row highlighted" : "Row"}>
<h3>{title}</h3>
{children}
</div>
);
}
function App() {
return (
<div className="List">
<Row title="First" isHighlighted>
<p>First item content</p>
</Row>
<Row title="Second">
<p>Second item content</p>
</Row>
</div>
);
}