Both provide solutions for code reuse.
Example: NameForm
A function that returns a component that renders your component. The HOC encapsulates special behavior (e.g. state management, event handlers, etc) and renders your component with props. Expects a component constructor (either a function or a class).
Example: NameForm with HOC
- Allows you to share common functionality and behavior between components without repetition.
- Makes it easy to unit test. The wrapped component can be mounted and tested in complete isolation from the HOC, allowing you to simply pass in mocks via props in the unit test.
- Creates a level of indirection. It's not obvious where the wrapped component receives certain data from. Without looking at the HOC source code, there's no way to tell which props it gets from the parent that renders it and which it gets from the HOC.
- HOCs need to be (or at least should be) configured outside of the component, which means they can't be configured based on component state.
- HOCs wrap your entire component, so you have less flexibility in composition.
A component that calls a function to render children. The RPP encapsulates special behavior (e.g. state management, event handlers, etc) and calls the render prop function with arguments. Expects a function that returns a React element.
Example: NameForm with RPP
- Allow you to share common functionality and behavior between components without repetition.
- Make it obvious where data comes from in your component. Props come from the parent that renders it, and data from the RPP comes from the render function parameters.
- RPP can be configured based on both props and component state.
- RPP can be composed within your component's render method however you'd like.
- Can clutter your render method depending on the amount of configuration needed in the RPP and the amount of nesting.
- Makes it very difficult to unit test your component in isolation from the RPP. Mounting your component also mounts the RPP, so the RPP must somehow be stubbed to provide mock data. Aside from some convoluted import/export techniques and replacing the original RPP module export with a double, completely stubbing out the RPP behavior is impossible.
A more complex example involves GraphQL queries and Apollo.
With the HOC, the query cannot be configured based on state. If that's required, the state either needs to be elevated to a parent component or the query needs to be lowered to a child component.
With the RPP, the query can be configured based on props and state. But mounting the component for unit tests will trigger Apollo's behavior of executing the query, which we don't want, and will make it difficult to specify mock data.
- Introducing render props (the post that started it all): https://cdb.reacttraining.com/use-a-render-prop-50de598f11ce
- Testing render prop consumers (TLDR don't unit test them): https://blog.kentcdodds.com/testing-%EF%B8%8F-components-using-render-props-5623ab1814c
- Testing render prop consumers with Enzyme: https://medium.com/@dferber90/test-a-render-prop-6a44e02f4c39