DEV Community

Cover image for Override or set property to React element
Simon Ström
Simon Ström

Posted on • Edited on

Override or set property to React element

Photo by Thomas Tastet (Unsplash)

So, for a long time I thought when you have created an instance of an component (an element) you had no way of altering it. This was usually a problem for me when building reusable components.

But there is a React helper method for this!

React.cloneElement

The utility I am referring to is the cloneElement function exposed. You can use this to Clone and return a new React element using element as the starting point as stated on the docs.

The function accepts three arguments (one mandatory)

  1. The element to clone (This one is mandatory, of course...)
  2. The props to spread to the cloned element props.
  3. The new children to append to the element. If omitted the original children will remain.

For example lets override the click event and text on an imaginary Button component:


const buttonElement = (
   <button onClick={() => alert('hello')>Click me!</button>
)

React.cloneElement(
   buttonElement, 
   { 
      onClick: () => alert('This replaced the original onClick prop')
   },
   "I am the new text"
) 
Enter fullscreen mode Exit fullscreen mode

That is all there is to it really. The cloned element will have all the same props but a new click handler. And the children have been replaces with a new text.

Lets build something

The code for this example can be found here

We will build a popup menu with a list of actions. The consumer will only add regular button or anchor elements as children and we will enhance they all with consistent styling and event handlers to open/close the popup.

First just write a little helper. This piece of code will just ensure the children to be an array so we can use map of it

function toArray(items) {
  if (!items) return [];
  if (Array.isArray(items)) return items;
  return [items];
}
Enter fullscreen mode Exit fullscreen mode

Next up, the component. And it is quite straight forward. Simple state hook to handle the open/closed state const [open, setOpen] = useState(false).

Somewhere in the component we will alter our child components:

{toArray(children).map((c) =>
   React.cloneElement(c, 
      {
         className: "button",
         style: undefined,
         onClick: function (e) {
         setOpen(false);
         c.props.onClick?.(e)
      }
   })
)}

Enter fullscreen mode Exit fullscreen mode

We simply clone the element, override the styles and className property to ensure a consistent styling.

The onClick metod is enhanced, meaning we add our own implementation that closes the menu but also calls the existing onClick method, if it is defined, using optional chaining (hence the question mark)

The full code for the Menu component:

function Menu({ children }) {
  const [open, setOpen] = useState(false);

  return (
    <div className="button-menu">
      <button
        className="menu-toggle"
        aria-controls="menu"
        aria-expanded={open}
        onClick={() => setOpen(!open)}
      >
        {open ? "Close" : "Open"}
      </button>
      <div
        id="menu"
        className="button-group"
        style={{ display: open ? "inherit" : "none" }}
      >
        {/*
        This is the important part
        */}
        {toArray(children).map((c) => {
          return React.cloneElement(c, {
            className: "button",
            style: undefined,
            onClick: function (e) {
              setOpen(false);
              //eslint-disable-next-line
              c.props.onClick?.(e);
            }
          });
        })}
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

The only quirck with this approach is that you need to set keys for the elements inside the Menu component:

export default function App() {
  return (
    <Menu>
      <button key="a" 
          onClick={() => alert("I am from the button")}
      >
        I am button
      </button>
      <a key="b" href="#something">
        I am an anchor
      </a>
      <div key="c">Divs should not pose as buttons...</div>
    </Menu>
  );
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)