reactjshigher-order-componentsobject-composition

Is this considered mutation from a Higher Order Component?


I was reading the section on Don’t Mutate the Original Component. Use Composition from this link.

https://reactjs.org/docs/higher-order-components.html

I then reviewed a project I'm trying to build. At a high level, this is what my code looks like:

class Wrapper extends Component {

    constructor(props) {
        this.wrappedComponent = props.wrappedComponent;
    }

    async componentWillAppear(cb) {
        await this.wrappedComponent.prototype.fetchAllData();

        /* use Greensock library to do some really fancy animation on the wrapper <Animated.div> */

        this.wrappedComponent.prototype.animateContent();

        cb();
    }

    render() {
        <Animated.div>
        <this.wrappedComponent {...this.props} />
        </Animated.div>
    }
}

class Home extends Component {

    async fetchAllData(){
        const [r1,r2] = await Promise.All([
            fetch('http://project-api.com/endpoint1'),
            fetch('http://project-api.com/endpoint2')
        ]);

        this.setState({r1,r2});
    }
    animateContent(){
        /* Use the GreenSock library to do fancy animation in the contents of <div id="result"> */
    }

    render() {
        if(!this.state)
            return <div>Loading...</div>;

        return (
            <div id="result">
            {this.state.r1.contentHTML}
            </div>
        );
    }

}

export default class App extends Component {
    render() {
        return <Wrapper wrappedComponent={Home} />;
    }
}

My questions are:

  1. In my Wrapper.componentWillAppear(), I fire the object methods like this.wrappedComponent.prototype.<methodname>. These object methods can set it's own state or animate the contents of the html in the render function. Is this considered mutating the original component?
  2. If the answer to question 1 is yes, then perhaps I need a better design pattern/approach to do what I'm trying to describe in my code. Which is basically a majority of my components need to fetch their own data (Home.fetchAllData(){then set the state()}), update the view (Home.render()), run some generic animation functions (Wrapper.componentWillAppear(){this.animateFunctionOfSomeKind()}), then run animations specific to itself (Home.animateContent()). So maybe inheritance with abstract methods is better for what I want to do?

Solution

  • I would probably actually write an actual Higher Order Component. Rather than just a component which takes a prop which is a component (which is what you have done in your example). Predominately because I think the way you have implemented it is a bit of a code smell / antipattern.

    Something like this, perhaps.

    class MyComponent extends React.Component {
        constructor() {
            super();
            this.animateContent = this.animateContent.bind(this);
        }
    
      componentWillReceiveProps(nextProps) {
        if (this.props.r1 !== nextProps.r1) {
            this.animateContent();
        }
      }
    
        componentDidMount() {
            // do your fetching and state setting here
        }
    
        animateContent() {
            // do something
        }
    
        render() {
            if(!this.props.r1) {
                return <div>Loading...</div>;
            }
    
            return (
                <div id="result">
                {this.props.r1.title}
                </div>
            );
        }
    }
    
     const myHOC = asyncFn => WrappedComponent => {
       return class EnhancedComponent extends React.Component {
         async componentDidMount(){
           const [r1, r2] = await asyncFn();
           this.setState({ r1, r2 })
           this.animateContent();
         }
    
         animateContent = () => {
            // do some animating for the wrapper.
         }
    
         render() {
           return (<WrappedComponent {...this.props} {...this.state} />)
         }
       }
     }
    
    
     const anAsyncExample = async () => {
            const result = await fetch("https://jsonplaceholder.typicode.com/posts");
        return await result.json();
     }
    
     const MyEnhancedComponent = myHOC(anAsyncExample)(MyComponent);
    

    Here's a working JSFiddle so you can see it in use: https://jsfiddle.net/patrickgordon/69z2wepo/96520/

    Essentially what I've done here is created a HOC (just a function) which takes an async function and returns another function which takes and a component to wrap. It will call the function and assign the first and second result to state and then pass that as props to the wrapped component. It follows principles from this article: https://medium.com/@franleplant/react-higher-order-components-in-depth-cf9032ee6c3e