Understanding cloneElement, isValidElement and Render Props
React provides several APIs for transforming elements: cloneElement, isValidElement.
In this story, we’ll have a look at using these to compose React components, how they work and when to use them. We then look at how the render props pattern can help transform elements the way we need them.
Add new props to an existing component using cloneElement
Let's say, for example, you want to add a new class and an on-click handler to the children of an existing component.
One of the ways we can add new props to the children
is by using React.cloneElement()
.
Here's how we can implement it:
function Buttons({ children }) {
const newProps = {
className: "highlight",
onClick: () => console.log("Clicked!")
};
return (
<>
{children.map((child, index) =>
React.cloneElement(child, { ...newProps, key: index })
)}
</>
);
}
Using React.cloneElement()
does the following:
- Original element's props are shallow-merged with new props
- New children replace existing children
key
andref
from the original element are preserved
The syntax looks as follows:
React.cloneElement(
element, // A new React element using element as the starting point
[config], // all new props + key or ref
[...children]
);
Check for valid React elements using isValidElement
This function returns true
if an element is a valid React Element and React can render it. Returns true
or false
.
React.isValidElement(object);
Here’s an example of modifying the elements from the previous example:
function Buttons({ children }) {
const newProps = {
className: 'highlight',
onClick: () => console.log('Clicked!'),
};
return (
<>
{children.map(
(child, index) =>
React.isValidElement(child) &&
React.cloneElement(child, { ...newProps, key: index })
)}
</>
);
}
Share code between components using Render Props
With render props, we could render anything we wanted, just like the children prop method. But we will also be able to pass props to whatever is being rendered. The main purpose of Render Props is being able to communicate with any rendered component without having to couple implementation details.
Let's change the above example to use RenderProps:
function Buttons(props) {
return props.render();
}
export default function App() {
return (
<Buttons
render={() => (
<>
<button>Hello</button>
<button>World</button>
</>
)}
/>
</div>
);
}
Summary
You can use cloneElement
to inject values into your child components when the parent-child relationship implies a shared API.
If you desire freedom when it comes to deciding the way a part of your component is rendered Render props would be the ideal choice. You could achieve the same effect using cloneElement
, but cloneElement
forces you to define and use a fully-fledged component.