HOC /Props and Spread Operators

Hello guys. I am learning React now. I’ve been struggling to understand the logic in the following code I provide below.

import './App.css'


const Button = ({ children, ...rest }) => (
  <button onClick={() => console.log("ButtonClick")} {...rest}>
    {children}
  </button>
);

const withClick = (Component) => {
  const handleClick = () => {
    console.log("WithClick");
  };

  return(props) => {
    return<Component {...props} onClick={handleClick} />;
  };
};

const MyButton = withClick(Button);

export default function App() {
  return <MyButton onClick={() => console.log("AppClick")}>Submit</MyButton>;

This is how I understand it so far:

We create a functional component Button that accepts two parameters, in this case children prop and a copy of an array or object from elsewhere as a prop. The component returns a button with onClick event listener logging out “ButtonClick” to the console, as well as …rest array or object copy.
Between the opening and closing tags of the button, we use a children’s prop, which will serve as the text the button will hold.

Next, we declare a higher-order function that takes a Component as argument. Inside in the curly braces, we add a handle click arrow function that returns console.log(“WithClick”) to the console. The higher-order function returns a new component consisting of (…props) and onClick that uses the handleClick arrow function as an event handler.

What I understand so far is that this higher-order function will serve as a factory.

Now the questions: Why do we have props in the return statement as a parameter within withClick? It’s so confusing. Does it have to do anything with Button component props or no? What’s this props purpose?

Since we create a new component here: const MyButton = withClick(Button); props from withClick return statement, make sure that any additional props we add to Button will be inherited. I am so confused here.

Also, it looks like we can mutate onClick functionality for props in this case, but only for those that don’t consist of a copy of an array. If you click the button to run this app, you get “WithClick” as a message in the console. The onClick we later assign to the anonymous arrow function of the MyButton component inside the App component won’t have any effect.

Help appreciated! :raised_hands: :raised_hands:

This is an interesting pattern that I haven’t seen like this before. It should perform a task similar to the one described in the react docs alternatives for .cloneElement page.

Not quite. The Button component accepts an unlimited number of props. Take this example:

const Button = ({ children, ...rest }) => {
  const{onClick, id, className} = rest;
  console.table([['children', children], ['className', className], ['id', id], ['onClick', onClick]]);
  return (<button onClick={() => console.log("ButtonClick")} {...rest}>
    {children}
  </button>)
};

<Button 
      onClick={()=> {}}
      id="appButton"
      className="btn">
      Buttontext
</Button>

The console in the Button component logs the following table:

A factory function returns an object. withClick is a higher order function that can be curried. More on currying functions can be found here.

Take this example:

export default function App() {
  return withClick(Button)({children: <>Hello</>, id: "currying"}) 
}

Output:

<button id="currying">Hello</button>

Consider this example:

const multiply = (a) => {
    console.log(a) // 2
    return (b) => {
      console.log(b) // 3
      return a * b;
    }
}
const multiplyByTwo = multiply(2);
console.log(multiplyByTwo(3)) // 6

The first function call multiply(2) caches the given argument. In the React example that would be the button component.
The second function call multiplyByTwo(3) passes the argument 3 to the parameter of the inner function.

In case of the React component, that would be the props of MyButton.

You simply overwrite them.
Consider this example:

const withClick = (Component) => {
  const handleClick = () => {
    console.log("WithClick");
  };

  return(props) => {
    return<Component {...props} onClick={() => { handleClick(); props.onClick() }} />;
  };
};

Clicking the button now logs ‘WithClick’ and ‘AppClick’ to the console.

2 Likes

So, as far as I understood, we are talking about Closures and Currying in JS In this case (props) => {…} is inner function and it retains access to handleClick and the component argument from the outer scope.

The inner component returns a new component extending the original component with additional **onClick ** prop.

Then this new onClick prop from the returned new component first calls handleClick and then calls onClick function passed through props from a component out of the scope of withClick

Am I right about it?

The prop in the return statement within withClick make me feel so confused? What is it’s true purpose?

Yes.

The inner function returns the component that was passed as an argument to the outer function, with extended/overwritten props.

Then this new onClick prop from the returned original component (Button) references the handleClick and the onClick function passed through props from the component that was returned from the withClick function.

It makes the difference between returning a component and returning a function that can be called with the Button component as an argument (const MyButton = withClick(Button);). Note the camelCasing in the withClick function. This is the naming convention of a Vanilla JS function – not a functional component which uses PascalCasing.

return (props) => {
    return <Component {...props} onClick={handleClick} />;
};

The props as an argument in the returned function let’s you extend the passed Component (Button) with the props of MyButton.

1 Like

So basically , Props Button component initially has children and …rest prop Spread operator we are using on rest object prevents it from direct mutation. These props are attached to the component and preserved even when we are passing the component to withClick function.

const withClick = (Component) => {
  const handleClick = () => {
    console.log("WithClick");
  };

  return (props) => {
    return <Component {...props} onClick={handleClick} />;
  };
};

Since we are dealing with closures and currying, we have a sequence of functions. Inner function gives the access to the outer functions scope so therefore also access to Button component and its props so that’s why we have props as an argument in return statement.

We are returning a brand new component with extended functionality and props. If we suppose that initial …rest props object deconstructed looked like this const{className, Id }=rest we can now , if we want use them in our new MyButton component because we can access them.

In this case MyButton is parent of Button component while both of them are children of the App component.

Since onClicks don’t use spread operator syntax, they are being mutated/overwritten?
Take a look. The only output I get is WithClick and no AppClick Should it be both?

I wouldn’t say so. The children props that are directly destructured in the parameter list are required. In this case, the component that is returned from withClick needs to have children. You use the rest parameter for the optional props to make them accessible to the Button component and to be as generic as possible. This way the Button component receives and renders whatever you pass as props in the MyButton component and whatever you add or overwrite in the withClick function.

The parameter ‘props’ in the function returned from the withClick function are the props from the MyButton component. The props from the Button component are stored within the Component parameter.

I wouldn’t say so. MyButton does not render the Button component, but is the mutated Button component.

The onClick prop is also part of the props object. Adding an onClick prop in the returned Component overwrites the onClick prop in the props object.

This logs ‘AppClick’:

return(props) => {
    return<Component {...props} />;
};

Only if you add the onClick event handler from the props object to the onClick prop in the returned Component:

If you remove the onClick props from MyButton and the returned Component, you get ‘ButtonClick’ logged to the console.
And this way, you even get all 3 console.logs:

return(props) => {
    return <Component {...props} 
      onClick={() => {
        handleClick();
        props.onClick();
        Component('').props.onClick();
      }}/>;
  };