Override or set property to React element
Tags: react, javascript, webdev
Published: 2020-09-17 19:37:27

[*Photo by Thomas Tastet (Unsplash)*](https://unsplash.com/photos/hSODeSbvzE0)

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](https://reactjs.org/docs/react-api.html#cloneelement) 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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) 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:

```javascript

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"
) 
```

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](https://codesandbox.io/s/react-quick-tip-clone-element-p1g5e)

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

```javascript
function toArray(items) {
  if (!items) return [];
  if (Array.isArray(items)) return items;
  return [items];
}
```
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:
```javascript
{toArray(children).map((c) =>
   React.cloneElement(c, 
      {
         className: "button",
         style: undefined,
         onClick: function (e) {
         setOpen(false);
         c.props.onClick?.(e)
      }
   })
)}

```
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)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining)

The full code for the Menu component:

```javascript
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>
  );
}
```


{% codesandbox react-quick-tip-clone-element-p1g5e %}

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

```javascript
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>
  );
}
```