//

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:

  1. Original element's props are shallow-merged with new props
  2. New children replace existing children
  3. key and ref 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.