Creating Higher-Order Component

Learn to refactor our code from the previous chapter to make a higher-order component in this lesson.

We'll cover the following

Creating reusable higher-order component

At the bottom of this lesson, we have the test-driven-carousel project from the previous chapter, take a look at the Carousel component. In addition to rendering a somewhat complex DOM tree, it also has one piece of state and two event handlers that manipulate that state. Let’s try building an HOC that encapsulates that logic.

  • Well-implemented HOCs tend to be highly reusable, and this one will be no exception. It’ll manage the state for any component with an “index” prop, meaning a number that can go from 0 up to some limit. Call it HasIndex. Start with a minimal dummy implementation that you can run tests against:

    // src/HasIndex.js
    import React from 'react';
    
    export default (Component, indexPropName) =>
      class ComponentWithIndex extends 
    React.PureComponent {
        static displayName =
          `HasIndex(${Component.displayName || Component.name})`;
    
        render() {
          return <Component {...this.props} />;
        }
      };
    
  • To replace the slideIndex logic in Carousel, we need HasIndex to provide three props to the wrapped component: the index itself (with the given indexPropName), an increment function, and a decrement function. To support wrap-around, the increment and decrement functions should accept an upper bound argument. Write a test suite with those requirements:

    // src/tests/HasIndex.test.js
    import React from 'react';
    import { shallow } from 'enzyme';
    import HasIndex from '../HasIndex';
    
    describe('HasIndex()', () => {
      const MockComponent = () => null;
      MockComponent.displayName = 'MockComponent';
      const MockComponentWithIndex = HasIndex(MockComponent, 'index');
    
      it('has the expected displayName', () => {
        expect(MockComponentWithIndex.displayName).toBe(
          'HasIndex(MockComponent)'
        );
      });
    
      let wrapper;
      beforeEach(() => {
        wrapper = shallow(<MockComponentWithIndex />);
      });
    
      it('has an initial `index` state of 0', () => {
        expect(wrapper.state('index')).toBe(0);
      });
    
      it('passes the `index` state down as the `index` prop', () => {
        expect(wrapper.prop('index')).toBe(0);
        wrapper.setState({ index: 1 });
        expect(wrapper.prop('index')).toBe(1);
      });
    
      it('has an `index` state of 2 on decrement from 3', () => {
        wrapper.setState({ index: 3 });
        wrapper.prop('indexDecrement')();
        expect(wrapper.state('index')).toBe(2);
      });
    
      it('has an `index` state of 1 on increment', () => {
        wrapper.prop('indexIncrement')();
        expect(wrapper.state('index')).toBe(1);
      });
    
      it('has the max `index` state on decrement from 0', () => {
        wrapper.setState({ index: 0 });
        wrapper.prop('indexDecrement')(3);
        expect(wrapper.state('index')).toBe(2);
      });
    
      it('has the min `index` state on increment from the max', () => {
        wrapper.setState({ index: 2 });
        wrapper.prop('indexIncrement')(3);
        expect(wrapper.state('index')).toBe(0);
      });
    });
    
  • Then try modifying the implementation to meet those requirements. You should end up with something like this:

Get hands-on with 1200+ tech skills courses.