Before we begin, there are two points to note: 1. React advanced components are just a pattern, not the basics of React. 2. It’s not necessary to develop the React app. You can skip this article and still develop the React app. However, it’s not overwhelming, and if you’re also a React developer, it’s highly recommended that you master it.

Why are higher-order components needed

If you Don’t know Don’t Repeat Yourself or D.R.Y, you won’t get very far in software development. For most developers, it is a development guideline. In this article, we’ll learn how to apply the DRY principle — advanced components — in React. Before we begin, let’s recognize the problem.

Suppose we want to develop something like the following figure. Like most projects, we started with a process. By the time you get to the point of development, there will be a lot of tooltip scenes where the mouse hovers over an element.

There are several ways to do this. You might want to write a component with a hover state to control whether the tooltip displays or not. Then you need to add three components — Info, TrendChart and DailyChart.

Let’s start with the Info component. It’s simple, just an SVG icon.

class Info extends React.Component {
  render() {
    return (
      <svg
        className="Icon-svg Icon--hoverable-svg"
        height={this.props.height}
        viewBox="0 0 16 16"
        width="16"
      >
        <path d="M9 8A1 1 0 0 0 1-1h5.5a1 1 0 1 0 0 2 H7V4A1 1 0 0 2 zm4 0h8A4 4 0 0 1 4 4v8A4 4 0 0 1-4 4H4a4 4 0 1-4-4V4a4 4 0 0 1 4-4zm4 5.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0 3z" />
      </svg>); }}Copy the code

We then need to add a state to record whether the component is Hover, using onMouseOver and onMouseOut in the React mouse event.

class Info extends React.Component {
  state = { hovering: false };
  mouseOver = (a)= > this.setState({ hovering: true });
  mouseOut = (a)= > this.setState({ hovering: false });
  render() {
    return( <> {this.state.hovering === true ? <Tooltip id={this.props.id} /> : null} <svg onMouseOver={this.mouseOver} onMouseOut={this.mouseOut} className="Icon-svg Icon--hoverable-svg" Height ={this.props. Height} viewBox="0 0 16 16" width="16" > <path d=" m98a1 1 0 0 0-1-1h5.5a1 1 0 1 0 0 2H7v4a1 1 0 0 0 0 2 0zM4 0h8A4 4 0 0 1 4 4v8A4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm4 5.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0 3z" />  </svg> </> ); }}Copy the code

Looks good, we need to write the same logic in TrendChart and DailyChart.

class TrendChart extends React.Component {
  state = { hovering: false };
  mouseOver = (a)= > this.setState({ hovering: true });
  mouseOut = (a)= > this.setState({ hovering: false });
  render() {
    return( <> {this.state.hovering === true ? <Tooltip id={this.props.id} /> : null} <Chart type="trend" onMouseOver={this.mouseOver} onMouseOut={this.mouseOut} /> </> ); }}Copy the code
class DailyChart extends React.Component {
  state = { hovering: false };
  mouseOver = (a)= > this.setState({ hovering: true });
  mouseOut = (a)= > this.setState({ hovering: false });
  render() {
    return( <> {this.state.hovering === true ? <Tooltip id={this.props.id} /> : null} <Chart type="daily" onMouseOver={this.mouseOver} onMouseOut={this.mouseOut} /> </> ); }}Copy the code

We have developed all three components. But as you can see, it’s not DRY because we’ve repeated the same hover logic three times in three components.

The problem is obvious. When a new component needs hover logic, we should avoid duplication. So how do we solve it? To help you understand, let’s take a look at two programming concepts — callbacks and higher-order functions.

What are callbacks and higher-order functions

In JavaScript, functions are the first citizens. That is, like objects/arrays/strings, it can be assigned to variables, passed as arguments to functions, and returned by functions.

function add(x, y) {
  return x + y;
}

function addFive(x, addReference) {
  return addReference(x, 5);
}

addFive(10, add); / / 15
Copy the code

You might get a little convoluted: We pass a function argument named addReference in the function addFive and call it when we return internally. In this case, the function you pass as an argument is called a callback; A function that receives a function as an argument is called a higher-order function.

To make it more intuitive, let’s conceptualize the naming of the code above.

function add(x, y) {
  return x + y;
}

function higherOrderFunction(x, callback) {
  return callback(x, 5);
}

higherOrderFunction(10, add);
Copy the code

This is actually quite common. If you’ve used array methods, jQuery, or the LoDash library, then you’ve used callbacks and higher-order functions.

[1.2.3].map(i= > i + 5);

_.filter([1.2.3.4], n => n % 2= = =0);

$("#btn").on("click", () = >console.log("Callbacks are everywhere"));
Copy the code

Third, the simple application of higher order functions

Let’s go back to the example I wrote earlier. Not only do we need addFive, maybe we need addTen, addTwenty, and so on. The way we write it now, when we write a new function, we have to repeat the logic.

function add(x, y) {
  return x + y;
}

function addFive(x, addReference) {
  return addReference(x, 5);
}

function addTen(x, addReference) {
  return addReference(x, 10);
}

function addTwenty(x, addReference) {
  return addReference(x, 20);
}

addFive(10, add); / / 15
addTen(10, add); / / 20
addTwenty(10, add); / / 30
Copy the code

It looks good, but it’s still a bit repetitive. Our goal is to create more Adder functions (addFive, addTen, addTwenty, and so on) with less code. With this in mind, we create a makeAdder function that takes a number and a function as arguments, so without much ado, just look at the code.

function add(x, y) {
  return x + y;
}

function makeAdder(x, addReference) {
  return function(y) {
    return addReference(x, y);
  };
}

const addFive = makeAdder(5, add);
const addTen = makeAdder(10, add);
const addTwenty = makeAdder(20, add);

addFive(10); / / 15
addTen(10); / / 20
addTwenty(10); / / 30
Copy the code

Good. Now we can write as many Adder functions as we want, and we don’t have to write as much duplicate code.

This method of taking a function and applying one or more, but not all, arguments to it, creating and returning a new function in the process is called “partial function application.” An example of this approach is the.bind in JavaScript.

4. Higher-order components

So what does this have to do with the fact that we wrote the React code repeatedly in the first place? Create similar higher-order components just as you create the higher-order function makeAdder. It looks good. Let’s give it a try.

Higher-order functions

  • A function
  • Accepts a callback function as an argument
  • Returns a new function
  • The returned function can call the callback function passed in
function higherOrderFunction(callback) {
  return function() {
    return callback();
  };
}
Copy the code

High order component

  • A component
  • Accept a component as a parameter
  • Returns a new component
  • The returned component can render the component that was passed in
function higherOrderComponent(Component) {
  return class extends React.Component {
    render() {
      return <Component />; }}; }Copy the code

5. Simple application of higher-order components

Ok, we now understand the basic concepts of higher-order components. As you will recall, the initial problem was to duplicate the Hover logic in too many places.

state = { hovering: false };
mouseOver = (a)= > this.setState({ hovering: true });
mouseOut = (a)= > this.setState({ hovering: false });
Copy the code

Keep in mind that we want a higher-order component (named withHover) to compress the Hover logic and have hovering status so that we don’t duplicate the Hover logic.

The end goal, whenever we want to write a component withHover state, is to pass that component as an argument to our higher-order component withHover.

const InfoWithHover = withHover(Info);
const TrendChartWithHover = withHover(TrendChart);
const DailyChartWithHover = withHover(DailyChart);
Copy the code

Then, whatever component passes withHover returns the component itself and receives a Hovering attribute.

function Info({ hovering, height }) {
  return( <> {hovering === true ? <Tooltip id={this.props.id} /> : null} <svg className="Icon-svg Icon--hoverable-svg" height={height} viewBox="0 0 16 16" width="16" > <path d="M9 8a1 1 0 0 0 1-1h5.5a1 1 0 1 0 2 H7v4A1 1 0 0 2 0zM4 0h8A4 4 0 0 1 4 4v8A4 4 0 0 1 4 0 0 1 4-4zm4 1 0 0 0 0 3 "/> </ SVG > </>); }Copy the code

Now we need to start writing the withHover component. As mentioned above, the following three points need to be done:

  • Accepts a component as an argument
  • Returns a new component
  • Parameter components receive a Hovering attribute

1. Accept a component as an argument

function withHover(Component) {}
Copy the code

2. Return a new component

function withHover(Component) {
  return class WithHover extends React.Component {};
}
Copy the code

3. Parameter components receive a Hovering attribute

Here’s the new problem: Where does Hovering come from? We can create a new component that uses Hovering as its state and pass it on to the original parameter component.

function withHover(Component) {
  return class WithHover extends React.Component {
    state = { hovering: false };
    mouseOver = (a)= > this.setState({ hovering: true });
    mouseOut = (a)= > this.setState({ hovering: false });
    render() {
      return (
        <div onMouseOver={this.mouseOver} onMouseOut={this.mouseOut}>
          <Component hovering={this.state.hovering} />
        </div>); }}; }Copy the code

A sentence comes to mind: a component is the process of converting props to UI; Higher-order components are the process of transforming one component into another.

We have covered the basics of higher-order functions, but there are still a few points worth discussing.

6. Advanced application of higher-order components

Looking back at the withHover component, there is one downside: It assumes that the parameter component that the user passes in must receive a prop called Hovering. If a parameter component already has a prop named Hovering and that prop isn’t used to handle hovers, it might cause naming conflicts. We can try letting users customize the hover prop name.

function withHover(Component, propName = "hovering") {
  return class WithHover extends React.Component {
    state = { hovering: false };
    mouseOver = (a)= > this.setState({ hovering: true });
    mouseOut = (a)= > this.setState({ hovering: false });
    render() {
      const props = {
        [propName]: this.state.hovering
      };

      return (
        <div onMouseOver={this.mouseOver} onMouseOut={this.mouseOut}>
          <Component {. props} / >
        </div>); }}; }Copy the code

In withHover, we give propName a default value. Users can also use a second parameter in a component to customize their naming.

function withHover(Component, propName = "hovering") {
  return class WithHover extends React.Component {
    state = { hovering: false };
    mouseOver = (a)= > this.setState({ hovering: true });
    mouseOut = (a)= > this.setState({ hovering: false });
    render() {
      const props = {
        [propName]: this.state.hovering
      };

      return( <div onMouseOver={this.mouseOver} onMouseOut={this.mouseOut}> <Component {... props} /> </div> ); }}; } function Info({ showTooltip, height }) { return ( <> {showTooltip === true ? <Tooltip id={this.props.id} /> : null} <svg className="Icon-svg Icon--hoverable-svg" height={height} viewBox="0 0 16 16" width="16" > <path d="M9 8a1 1 0 0 0 1-1h5.5a1 1 0 1 0 2 H7v4A1 1 0 0 2 0zM4 0h8A4 4 0 0 1 4 4v8A4 4 0 0 1 4 0 0 1 4-4zm4 1 0 0 0 0 3 "/> </ SVG > </>); } const InfoWithHover = withHover(Info, "showTooltip");Copy the code

Another problem you may have noticed is that in the component Info, it also receives a prop named Height. Height can only be undefined, but we expect something like this:

const InfoWithHover = withHover(Info)

...

return <InfoWithHover height="16px" />
Copy the code

We pass height to InfoWithHover, but how do we make it work?

function withHover(Component, propName = "hovering") {
  return class WithHover extends React.Component {
    state = { hovering: false };
    mouseOver = (a)= > this.setState({ hovering: true });
    mouseOut = (a)= > this.setState({ hovering: false });
    render() {
      console.log(this.props); // { height: "16px" }

      const props = {
        [propName]: this.state.hovering
      };

      return (
        <div onMouseOver={this.mouseOver} onMouseOut={this.mouseOut}>
          <Component {. props} / >
        </div>); }}; }Copy the code

As you can see from the console, this. Props is {height: “16px”}. All we need to do is pass it to the parameter Component, regardless of the value of this.props.

    render() {
      const props = {
        [propName]: this.state.hovering, ... this.props, }return (
        <div onMouseOver={this.mouseOver} onMouseOut={this.mouseOut}>
          <Component {. props} / >
        </div>
      );
    }
Copy the code

Ultimately, we can see that by using higher-order components you can effectively reuse the same set of logic and avoid too much repetitive code. But does it really have any disadvantages? Apparently not.

Seven, small defects of higher-order components

An inversion of control may occur when we use higher-order components. Imagine if we were using the withRouter of the React Router. According to documentation, whatever component it is, it will pass match, location, and history to the prop of that component.

class Game extends React.Component {
  render() {
    const { match, location, history } = this.props // From React Router. }}export default withRouter(Game)
Copy the code

As you can see from the above, if our component Game also has prop named match, Location, and History, we will cause a naming conflict. We encountered this problem when we wrote the withHover component and solved it by passing in a second parameter to custom naming. But when we use higher-order components from third-party libraries, we are not necessarily so lucky. We had to change the name of our own component Prop or stop using the higher-level component in a third-party library.

Eight, the end

React higher-order Components React higher-order Components React higher-order Components If you are confused about your learning and understanding, please feel free to contact me.