React Higher Order Components in depth
Inheritance Inversion
Inheritance Inversion (II) is implemented trivially like this:
As you can see, the returned HOC class (Enhancer) extends the WrappedComponent. It is called Inheritance Inversion because instead of the WrappedComponent extending some Enhancer class, it is passively extended by the Enhancer.
Inheritance Inversion allows the HOC to have access to the WrappedComponent instance via this, which means it has access to the state, props, component lifecycle hooks and the render method.
I won't go into much detail about what you can do with lifecycle hooks because it’s not HOC specific but React specific. But note that with II you can create new lifecycle hooks for the WrappedComponent. Remember to always call super.[lifecycleHook]
Reconciliation process
Before diving in let’s summarize some theory.
React Elements describe what is going to be rendered when React runs it’s reconciliation process.
React Elements can be of two types: String and Function. The String Type React Elements (STRE) represent DOM nodes and the Function Type React Elements (FTRE) represent Components created by extending React.Component. For more about Elements and Components read this
FTRE will be resolved to a full STRE tree in React’s reconciliation process (the end result will be always DOM elements).
This is very important and it means that Inheritance Inversion High Order Components don’t have a guaranty of having the full children tree resolved.
Inheritance Inversion High Order Components don’t have a guaranty of having the full children tree resolved.
This is will prove important when studying Render Highjacking.
What can you do with Inheritance Inversion?
- Render Highjacking
- Manipulating state
Render Highjacking
It is called Render Highjacking because the HOC takes control of the render output of the WrappedComponent and can do all sorts of stuff with it.
In Render Highjacking you can:
- Read, add, edit, remove props in any of the React Elements outputted by render
- Read, and modify the React Elements tree outputted by render
- Conditionally display the elements tree
- Wrapping the element’s tree for styling purposes (as shown in Props Proxy)
*render refers to the WrappedComponent.render method
You cannot edit or create props of the WrappedComponent instance, because a React Component cannot edit the props it receives, but you can change the props of the elements that are outputted from the render method.
Just as we studied before, II HOCs don't have a guaranty of having the full children tree resolved, which implies some limits to the Render Highjacking technique. The rule of thumb is that with Render Highjacking you will be able to manipulate the element’s tree that the WrappedComponent render method outputs no more no less. If that element’s tree includes a Function Type React Component then you won't be able to manipulate that Component’s children. (They are deferred by React’s reconciliation process until it actually renders to the screen.)
Example 1: Conditional rendering. This HOC will render exactly what the WrappedComponent would render unless this.props.loggedIn is not true. (Assuming the HOC will receive the loggedIn prop)
Example 2: Modifying the React Elements tree outputted by render.
In this example, if the rendered output of the WrappedComponent has an input as it’s top level element then we change the value to “may the force be with you”.
You can do all sorts of stuff in here, you can traverse the entire elements tree and change props of any element in the tree. This is exactly how Radium does its business (More on Radium in Case Studies).
NOTE: You cannot Render Highjack with Props Proxy.
While it is possible to access the render method via WrappedComponent.prototype.render, you will need to mock the WrappedComponent instance and its props, and potentially handle the component lifecycle yourself, instead of relying on React doing it. In my experiments it isn’t worth it and if you want to do Render Highjacking you should be using Inheritance Inversion instead of Props Proxy. Remember that React handles component instances internally and your only way of dealing with instances is via this or by refs.
Manipulating state
The HOC can read, edit and delete state of the WrappedComponent instance, and you can also add more state if you need to. Remember that you are messing with the state of the WrappedComponent which can lead to you breaking things. Mostly the HOC should be limited to read or add state, and the latter should be namespaced not to mess with the WrappedComponent’s state.
Example: Debugging by accessing WrappedComponent’s props and state
This HOC wrapps the WrappedComponent with other elements, and also displays the WrappedComponent’s instance props and state. The JSON.stringify trick was taught to me by Ryan Florence and Michael Jackson. You can see a full working implementation of the debugger here.