Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make React HOC - High Order Components work together?

I have a small presentation component like this:

function MyList({data, uppercaseMe, lowercaseMe}) {
    return <ul>
        {data.map(item =>
            <li>{item} -
                <button onClick={() => uppercaseMe(item)}>Uppercase me!</button>
                <button onClick={() => lowercaseMe(item)}>Lowercase me!</button>
            </li>)}
    </ul>;
}

And then I have three HOCs I would like to use to decorate MyList:

const WithData = (Component) => {
return class extends React.Component {

    constructor(props) {
        super(props);
        this.state= {data:['one', 'two', 'three', 'four', 'five']};
    };

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

const WithUppercase = (Component) => {
return class extends React.Component {

    uppercaseMe = (item) => {
        // how to access WithData state from here?
        console.log(item.toUpperCase());
    };

    constructor(props) {
        super(props);
    };

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

const WithLowercase = (Component) => {
return class extends React.Component {

    lowercaseMe = (item) => {
        // how to access WithData state from here?
        console.log(item.toLowerCase());
    };

    constructor(props) {
        super(props);
    };

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

Like this:

const ComposedList = WithLowercase(WithUppercase(WithData(MyList)));

Unfortunately, I don't know how to change the state of WithData from within WithLowercase or WithUppercase.

How do you expose the state of one HOC to the other HOCS in the chain?

like image 319
SrgHartman Avatar asked Aug 04 '17 20:08

SrgHartman


People also ask

How do you call a higher-order component in React?

We can invoke the HOC as follows: const SimpleHOC = higherOrderComponent(MyComponent); A higher-order component transforms a component into another component. Note that a HOC doesn't modify the input component.

Can we use higher-order component in functional component?

Higher-Order Components enable us to apply functional programming principles on components by embracing composition. React Hooks, in contrast, transformed pure (in the sense of functional programming) function components to stateful/side-effect burdened beasts. Anyway, both have their right to exist.

What are higher-order components hoc and how you can use them in practice?

A higher-order component (HOC) is an advanced technique in React for reusing component logic. HOCs are not part of the React API, per se. They are a pattern that emerges from React's compositional nature. Concretely, a higher-order component is a function that takes a component and returns a new component.


2 Answers

You don't want to expose the state directly. What you do want to do is pass down a function as a prop to the wrapped component that will allow you to update state.

So if we take the HOC and add a little bit of code, creating a function to update its state that can now be passed down to its child components:

const WithData = (Component) => {
  return class extends React.Component {

      constructor(props) {
          super(props);
          this.state= {data:['one', 'two', 'three', 'four', 'five']};
      }

      update (data) {
        this.setState({ data });
      }

      render() {
          return <Component onUpdate={this.update} {...this.props} data={this.state.data} />;
      }
  }
};

Now within one of the child components we can consume it:

const WithUppercase = (Component) => {
    return class extends React.Component {

        uppercaseMe = (item) => {
            const itemIndex = this.props.data.indexOf(item);
            const newData = this.props.data.slice();
            newData[itemIndex] = item.toUpperCase();
            this.props.onUpdate(newData); // update WithData state here
        };

        constructor(props) {
            super(props);
        };

        render() {
            // onUpdate function is once again passed to child components
            // implicitly through props if you need to call it again
            return <Component {...this.props} uppercaseMe={this.uppercaseMe}/>;
        }
    }
};
like image 89
FuriousD Avatar answered Oct 22 '22 20:10

FuriousD


Passing data back up the chain of components essentially goes against the pattern that react uses, which is one way data flow. Data goes down but never goes back up. Like @FuriousD said you should make the lowerCase and upperCase methods available to components further down the chain. You can do this using context.

"In some cases, you want to pass data through the component tree without having to pass the props down manually at every level. You can do this directly in React with the powerful "context" API."

Here is an example using an updated version of your code.

const WithUpperCase = (Component) => {
  class _WithUpperCase extends React.Component {
    getChildContext() {
      return {
        uppercaseMe: this.uppercaseMe,
      }
    }

    uppercaseMe = (item) => {
      return item.toUpperCase()
    };

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

  _WithUpperCase.childContextTypes = {
    lowercaseMe: PropTypes.func,
  }

  return _WithUpperCase;
};

const WithLowercase = (Component) => {
  class _WithLowercase extends React.Component {
    getChildContext() {
      return {
        lowercaseMe: this.lowercaseMe,
      }
    }

    lowercaseMe = (item) => {
      return item.toLowerCase()
    };

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

  _WithLowercase.childContextTypes = {
    lowercaseMe: PropTypes.func,
  }

  return _WithLowercase;
};

function MyList({ data }, { uppercaseMe, lowercaseMe }) {
  return (
    <ul>
      {
        data.map(item => (
          <li>{item} -
          <button onClick={() => uppercaseMe(item)}>Uppercase me!</button>
            <button onClick={() => lowercaseMe(item)}>Lowercase me!</button>
          </li>
        ))
      }
    </ul>
  );
}

MyList.contextTypes = {
  // Need to import the PropTypes library
  uppercaseMe: PropTypes.func,
  lowercaseMe: PropTypes.func,
};
like image 26
pizzarob Avatar answered Oct 22 '22 19:10

pizzarob