Higher order components

In the previous section, we saw how mixins are useful for sharing functionalities between components and the problems that they bring to our applications.

In the Functional Programming section of Chapter 2Clean Up Your Code, we mentioned the concept of higher order functions (HoFs), which are functions that, given a function, enhance it with some extra behaviors, returning a new one.

Let's see if we can apply the same concept to React components and achieve our goal of sharing functionalities between components while avoiding the downsides of mixins.

When we apply the idea of HoFs to components, we call this higher order components (HoCs) for brevity.

First of all, let's see what an HoC looks like:

  const HoC = Component => EnhancedComponent;

HoCs are functions that take a component as input and return an enhanced one as the output.

Let's start with a very simple example to understand what an enhanced component looks like.

Suppose you need to attach the same className property to every component for some reason. You could go and change all the render methods adding the className property to each of them, or you could write an HoC such as the following one:

  const withClassName = Component => props => ( 
<Component {...props} className="my-class" />
);

The preceding code can be a little difficult to understand initially; let's try to understand it.

We declare a withClassName function that takes a Component and returns another function.

The returned function is a stateless functional component that receives some props and renders the original component. The collected props are spread, and a className property with the "my-class" value is passed to it.

The reason why HoCs usually spread the props they receive on the component is because they tend to be transparent and only add the new behavior.

This is pretty simple and not very useful, but it should give you a better understanding of what HoCs are and what they look like.

Let's now see how we can use the withClassName HoC in our components.

First of all, we create a stateless functional component that receives the class name and applies it to a div tag:

  import { string } from 'prop-types';

const MyComponent = ({ className }) => (
<div className={className} />
);

MyComponent.propTypes = {
className: string
};

Instead of using it directly, we pass it to an HoC, as follows:

  const MyComponentWithClassName = withClassName(MyComponent);

Wrapping our components into the withClassName function, we ensure that it receives the className property.

Now, let's move on to something more exciting and let's try to transform the WindowResize mixin we saw in the previous section into an HoC function that we can reuse across our application.

The mixin was simply listening to the window resize event and making the updated innerWidth property of the window available into the state.

One of the biggest problems with that mixin was, that it was using the state of the component to provide the innerWidth value.

Doing that is bad because it pollutes the state with additional attributes, and those attributes may also clash with the attributes that are used in the components itself.

First of all, we have to create a function that receives a Component:

  const withInnerWidth = Component => ( 
class extends React.Component { ... }
);

You may have spotted a pattern in the way HoCs are named. It is a common practice to prefix HoCs that provide some information to the components they enhance using the with pattern.

Our withInnerWidth function will return a class component instead of a functional stateless component because, as we saw in the previous example, we need additional functions and state.

Let's see what the returned class looks like.

In the constructor, the initial state gets defined, and the handleResize callback is bound to the current class:

  constructor(props) { 
super(props);

this.state = {
innerWidth: window.innerWidth
};

this.handleResize = this.handleResize.bind(this);
}

The life cycle hooks and the event handler are identical to the mixin's:

  componentDidMount() { 
window.addEventListener('resize', this.handleResize);
}

componentWillUnmount() {
window.removeEventListener('resize', this.handleResize);
}

handleResize() {
this.setState({
innerWidth: window.innerWidth
});
}

Finally, the original component gets rendered in the following way:

  render() { 
return <Component {...this.props} {...this.state} />;
}

As you may note here, we are spreading the props as we saw before, but we are also spreading the state.

We are storing the innerWidth value inside the state to achieve the original behavior, but we do not pollute the state of the component; we use props instead.

As you learned in Chapter 3, Create Truly Reusable Components, using props is always a good solution to enforce reusability.

Now, using an HoC and getting the innerWidth value is pretty straightforward.

We create a stateless functional component that expects innerWidth as a property:

  const MyComponent = ({ innerWidth }) => { 
console.log('window.innerWidth', innerWidth);
...
};

MyComponent.propTypes = {
innerWidth: number
};

We enhance it as follows:

  const MyComponentWithInnerWidth = withInnerWidth(MyComponent);

There are various advantages of doing this rather than using a mixin. First of all, we do not pollute any state, and we do not require the component to implement any function.

This means that the component and the HoC are not coupled, and they can both be reused across the application.

Again, using props instead of state lets us make our component dumb so that we can use it in our style guide, ignoring any complex logic and just passing down the props.

In this particular case, we could create a component for each of the different innerWidth sizes we support.

Consider the following example:

  <MyComponent innerWidth={320} />

Or consider the following:

  <MyComponent innerWidth={960} />
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.141.47.221