Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React: Do Hooks Replace HOCs and Render Props?

From the React Hooks FAQ, we learn that hooks can replace HOCs and Render Props that return/render a single component.

I was trying to understand this a bit better, and why it is a fact.

Let's look at HOCs first:

A HOC is a function that takes a component as an argument, wraps it in surrounding logic like effects and state, and returns a new component. How exactly would a custom hook replace that? We still need the function that wraps an input function with other logic.

Looking at render props:

A render prop is a component we pass as a prop to another component, that then renders the passed component with some new props. I guess we can replace that with hooks by creating a custom hook that returns a full component, then use that hook in any needed component. So a parent does not have to pass a component as a prop to it's child. Is that how hooks would replace Render Props?

An explanation, preferably with code example(s), on how hooks can replace HOCs and Render Props in their most common use cases would be deeply appreciated.

like image 305
Magnus Avatar asked Jun 02 '19 22:06

Magnus


1 Answers

There are many different uses of HOCs and render props, so i can't possibly cover them all, but basically that paragraph is pointing out that many of the cases where you'd use a HOC/render prop can also be achieved with hooks. This doesn't make HOCs/render props obsolete but hooks are another tool at your disposal for sharing code between components.

One common job of a HOC/render prop is to manage the lifecycle of some data, and pass that data to the derived component or child component. In the following example, the goal is to get the window width, including the state-management and event listening involved with that.

HOC version:

function withWindowWidth(BaseComponent) {
  class DerivedClass extends React.Component {
    state = {
      windowWidth: window.innerWidth,
    }

    onResize = () => {
      this.setState({
        windowWidth: window.innerWidth,
      })
    }

    componentDidMount() {
      window.addEventListener('resize', this.onResize)
    }

    componentWillUnmount() {
      window.removeEventListener('resize', this.onResize);
    }

    render() {
      return <BaseComponent {...this.props} {...this.state}/>
    }
  }
  // Extra bits like hoisting statics omitted for brevity
  return DerivedClass;
}

// To be used like this in some other file:

const MyComponent = (props) => {
  return <div>Window width is: {props.windowWidth}</div>
};

export default withWindowWidth(MyComponent);

Render prop version:

class WindowWidth extends React.Component {
  propTypes = {
    children: PropTypes.func.isRequired
  }

  state = {
    windowWidth: window.innerWidth,
  }

  onResize = () => {
    this.setState({
      windowWidth: window.innerWidth,
    })
  }

  componentDidMount() {
    window.addEventListener('resize', this.onResize)
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this.onResize);
  }

  render() {
    return this.props.children(this.state.windowWidth);
  }
}

// To be used like this:

const MyComponent = () => {
  return (
    <WindowWidth>
      {width => <div>Window width is: {width}</div>}
    </WindowWidth>
  )
}

And last but not least, hook version

const useWindowWidth = () => {
  const [width, setWidth] = useState(window.innerWidth);
  useEffect(() => {
    const onResize = () => setWidth(window.innerWidth);
    window.addEventListener('resize', onResize);
    return () => window.removeEventListener('resize', onResize);
  }, [])
  return width;
}

// To be used like this:

const MyComponent = () => {
  const width = useWindowWidth();
  return <div>Window width is: {width}</div>;
}
like image 119
Nicholas Tower Avatar answered Oct 27 '22 01:10

Nicholas Tower