Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using hooks in a higher order component

Tags:

reactjs

I would like to develop a new feature, which previously would have lived in a higher order component, using hooks. However, since according to the React Documentation:

"You can’t use Hooks inside of a class component, but you can definitely mix classes and function components with Hooks in a single tree."

So let's say I have some existing class component ExistingComponent that I want to extend with additional functionality, say, listening to window.resize. I would expect to do it like this.

// Existing Component
export class ExistingComponent extends React.Component {
  render() {
    return <div>I exist!</div>;
  }
}

// Hook
export const useWindowResize = () => {
  function resize() {
    console.log('window resized!');
  }

  useEffect(() => {
    window.addEventListener('resize', resize);
    return function cleanup() {
      window.removeEventListener('resize', resize);
    };
  });
};

// HOC
export const withWindowResize = component => {
  useWindowResize();
  return component;
};

// Extended Component
export const BetterComponent = withWindowResize(ExistingComponent);

However, this fails with Uncaught Invariant Violation: Hooks can only be called inside the body of a function component. I do use react-hot-loader, but I am still able to use hooks in component functions that don't return a class component. Also, I can remove the useWindowResize() from the function and it renders as expected.

I am also able to render the example provided in the docs, so I know it's not a problem with hooks generically:

function Example() {
  // Declare a new state variable, which we'll call "count"
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

Is this the wrong approach?

like image 934
CaptainStiggz Avatar asked Feb 23 '19 01:02

CaptainStiggz


People also ask

Can you use Hooks in higher-order components?

In a modern React world, everyone uses function components with React Hooks. However, the concept of higher-order components (HOC) is still applicable in a modern React world, because they can be used for class components and function components.

Can we use Hooks inside hoc?

HOC is a function that takes a component as an argument and returns an enhanced version. HOC helps to isolate logic and state management in a separate class-based component. With React Hooks, state management can occur outside of a class. Hooks empower developers to use the functional programming aspects in React.

Do Hooks replace render props and higher-order components hoc?

Both render props and higher-order components render only a single child but in most of the cases Hooks are a simpler way to serve this by reducing nesting in your tree.

Can you use a hook in a class component?

You can't use Hooks inside a class component, but you can definitely mix classes and function components with Hooks in a single tree. Whether a component is a class or a function that uses Hooks is an implementation detail of that component.


1 Answers

You can return a new function component from your withWindowResize HOC in which you call the hook and spread the props on the component you pass in.

You can also pass an empty array as second argument to useEffect to only have it run once after the initial render.

const { useEffect } = React;

class ExistingComponent extends React.Component {
  render() {
    return <div>I exist!</div>;
  }
}

const useWindowResize = () => {
  useEffect(() => {
    function resize() {
      console.log('window resized!');
    }
    window.addEventListener('resize', resize);
    return () => {
      window.removeEventListener('resize', resize);
    };
  }, []);
};

const withWindowResize = Component => {
  return (props) => {
    useWindowResize();
    return <Component {...props} />;
  }
};

const BetterComponent = withWindowResize(ExistingComponent);

ReactDOM.render(<BetterComponent />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>
like image 143
Tholle Avatar answered Oct 14 '22 01:10

Tholle