Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling a Higher-Order Component within Component render

Something simple is tripping up my logic here:

I have a HOC AnimateOnLoad which renders a default content (used within react-router) within the page component.

const DefaultLayout = ({ component: Component, ...rest }) => (

   <Route
      {...rest}
      render={matchProps => (
         <div className="page-container">
            {AnimateOnLoad(
               <div className="container">
                  <Head />
                  <Nav />
                  <Component {...matchProps} />
               </div>
            )}
            <Footer />
         </div>
      )}
   />
);

The animateONLoad HOC looks like this:

const AnimateOnLoad = WrappedComponent =>
   class Animator extends Component {
      constructor(props) {
         super(props);
         this.ele = React.createRef();
      }
      componentDidMount() {
         Kute.fromTo(
            this.ele.current,
            { opacity: 0.3 },
            { opacity: 1, duration: 120 }
         ).start();
      }
      render() {
         return (
            <div className="animator" ref={this.ele}>
               <WrappedComponent {...this.props} />
            </div>
         );
      }
   };

However, I am getting an error:

Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it.

which doesn't make sense to me as I am returning a Component from AnimateOnLoad();

Thank you.

like image 664
Kayote Avatar asked Jan 27 '23 17:01

Kayote


2 Answers

Root cause of your error is this

This may happen if you return a Component instead of <Component />

@ShubhamKhatri Answer will resolve your issue. I want to expand his answer.

Don’t Use HOCs Inside the render Method

React’s diffing algorithm (called reconciliation) uses component identity to determine whether it should update the existing subtree or throw it away and mount a new one. If the component returned from render is identical (===) to the component from the previous render, React recursively updates the subtree by diffing it with the new one. If they’re not equal, the previous subtree is unmounted completely.

render() {
  // A new version of Animate is created on every render
  // Animate1 !== Animate2
  const Animate = AnimateOnLoad(MyComponent);
  // That causes the entire subtree to unmount/remount each time!
  return <Animate />;
}

Notice how HOC is used outside render in his answer.

The problem here isn’t just about performance — remounting a component causes the state of that component and all of its children to be lost.

That's why its very important to apply HOCs outside the component definition so that the resulting component is created only once. Then, its identity will be consistent across renders. This is usually what you want, anyway.

Reference: https://reactjs.org/docs/higher-order-components.html#dont-use-hocs-inside-the-render-method

like image 99
Abdul Rauf Avatar answered Jan 30 '23 06:01

Abdul Rauf


You aren't using the AnimateOnLoad HOC correctly. You need to use it like

const CustomComp = (matchProps) => (
     <div className="container">
          <Head />
          <Nav />
          <Component {...matchProps} />
     </div>
);

const HOCComponent = AnimateOnLoad(CustomComp)


const DefaultLayout = ({ component: Component, ...rest }) => (

   <Route
      {...rest}
      render={matchProps => (
         <div className="page-container">
            <HOCComponent {...matchProps}/>
            <Footer />
         </div>
      )}
   />
);
like image 41
Shubham Khatri Avatar answered Jan 30 '23 08:01

Shubham Khatri