On this page:
Software development is, in essence, the process of breaking a problem down into smaller problems, implementing solutions for those smaller problems, and then composing those solutions to form partial solutions, and so on, until eventually completing the whole solution.
What do React components do? Components break down the UI into smaller independent components. Components are composed with other components to create greater components, and so on until they eventually complete the whole UI.
Composition is the act of combining parts or elements to form a whole.
Components are the UI building blocks in React applications, like pure functions are the building blocks of function composition.
Function composition is the act of applying a function to the output of another function. In algebra, given two functions, f and g, (f ∘ g)(x) = f(g(x)). The circle is the composition operator and it's commonly pronounced "f composed with g" or "f after g". I pronounce it "composed with", maybe because in React I often see code like the following where I literally read “compose with”:
export default compose( withRouter, withApollo, withEtcEtc, )(Component)
You can compose functions as long as the output of one function is the expected input of the next function. You can’t compose two functions if one returns an array and the next one’s input expects a string. We can illustrate this idea with the following image:
Considering that the output of g is the input of f, which function do you think is executed first, f or g? g is executed first. Imagine you are the JS runtime and try to run the following code f(g(x)). You can’t run f until you resolve its argument.
We can use a compose function to declaratively compose (f ∘ g)(x)
h = compose(f,g)
One of the fundamental concepts in React is the declarative approach. Composition can be very declarative, but as in many cases, it's up to us. We could also code some imperative composition (don’t do it). Example:
// don’t do this! const compose = Component => { const afterG = withRouter(Component); const afterF = withApollo(afterG); return afterF; };
When we use declarative composition
we can state that f doesn't know g exists and g doesn't know f exists.h = compose(f,g)
You can apply composition in pure JavaScript code in your React real-world applications. For example to compose the validators of a form field. We use function composition to validate forms in our website. Applying composition in your real-world JavaScript projects is very powerful. Composition is not an academic or theoretical concept that you can’t explicitly apply in your JavaScript code. We cover this case in the function composition exercise of our advanced hands-on React training.
In React there is the notion of a tree made up of components. In this tree of components the following rules define the components’ relationship:
Since function composition uses a circle as operator, I'm also going to use circles to represent this composition model. There are two different perspectives I can think of to illustrate it:
I guess the first one makes more sense in this case because every parent has more than one child. But, what if parents have only one child?
To me, in this case, the second image (concentric circles) illustrates better the case.
Common sense tip. Looking from different perspectives when trying to understand something is very useful. Using the same lens is likely to show the same views.
Components have well-defined interfaces that enable explicit interactions between components. Those well-defined interfaces are called props. Props are the mechanism a component has to interact with the outer world - by outer world I mean the parent component.
Components have a prop called children. Because it is a prop, given this component
const User = props => <p>{props.children}</p>
, we can do either:
<User children="@alex_lobera" />
or
<User>@alex_lobera"</User>
You can see the later as syntactic sugar of the former. Of course, you can also do this (or any prop name variation you can think of)
const User = props => <p>{props.name}</p>
and so
<User name="@alex_lobera" />
Based on my experience teaching React at the React GraphQL Academy, I've seen many developers missing part of what the React composition model is and misunderstanding the “children” prop.
If we look at the following code:
const TwitterProfile = props => ( <section> <Text> Username </Text> <Text> @alex\_lobera </Text> </section> )
we can state:
If we look at the following component in isolation
const Text = props => <p>{props.children}</p>
we can state:
There is something very nice about “children”, it makes composition more declarative in the component tree.
HoCs is a pattern for reusing component logic. Component logic means logic that has to do with lifecycle methods and/or state and/or context.
Heads up! You don’t need a HoC if the logic you want to reuse doesn’t use lifecycle methods and/or component state and/or context.
A typical use case for using HoCs is fetching some data on componentDidMount and store it in the state. Here there is an example called withData that we use in our advanced material.
HoCs are functions that receive a component as an argument and return a new component.
In the following example withRouter is the HoC function and Threads is the component we are enhancing
export default withRouter(Threads)
You can compose a component with as many HoCs as you need, for instance:
export default withRouter( withApollo(QUERY)( connect(mapStateToProps)(Threads) ) )
If you find it hard to read the previous example, you can rewrite it using a compose function as follows:
export default compose( withRouter, withApollo(QUERY), connect(mapStateToProps), )(Threads)
There are many libraries, like recompose or react-apollo, that implement a compose function. The compose function is very simple, you can implement it yourself with a few keystrokes:
export const compose = (...functions) => component => functions.reduceRight( (enhancedComponet, func) => func(enhancedComponet), component );
Notice the reduceRight in the compose function. Composition goes from right to left, or you can also see it as from inside out. connect’s input is the Threads component. The output of connect is a new component which is the input of withApollo. The output of withApollo is a new component that is the input of withRouter. The output of withRouter is a new component composed with the previous components. That’s the reason we need to reduce the arguments of the compose function in the reverse order, and for that purpose we used reduceRight.
HoCs return one component, that's why concentric circles is the prefered way by many people to illustrate the previous example.
Notice the previous concentric circles represent the higher-order component functions but not the output of those functions (meaning the components that are rendered). We said that a higher-order component is a function that returns a new component, but that new component can contain other new components itself. That’s the case of withRouter and withApollo.
Wait, HoCs are functions, not components so how can we compose them? Same as the function composition we explained at the beginning of the article, although in this case the input & output of all the HoCs are always components:
Do you think the order of the HoCs matter? For instance, do the following two cases work the same:
Case A
export default compose( withRouter, withApollo(QUERY), connect(mapStateToProps), )(Threads)
Case B
export default compose( connect(mapStateToProps), withRouter, withApollo(QUERY), )(Threads)
From a composition perspective both cases are the same since all the HoCs’ input & output are components. Now from a React implementation perspective there are a few considerations:
If you are interested in checking the material we use to teach HoCs click on this link. We cover these and similar cases in more detail in any of the in-person React training we run, such as the React bootcamp, advanced React training, part-time React training, and of course the on-site corporate React training.
Render Props, like HoC, helps us reusing component logic (again! only logic that has to do with lifecycle methods and/or component state and/or context). The different is in the way they do it.
With HoCs composition happens all in one place, typically at the bottom of the file where we define the component:
export default withRouter( withApollo(QUERY)( connect(mapStateToProps)(Threads) ) )
A problem some people observe with HoCs is that composition doesn’t look very declarative from a React perspective. In React we tend to declare intent using components in JSX. In some cases, Render Props can make the code more readable. For instance:
const CoolComponent = () => ( {/* some cool JSX here */} {/* With Render Props we can apply the Measure logic only to the figure. With HoC we would need to wrap the entire CoolComponent */} <Measure> {(width, height) => ( <figure> <img alt="dog" style={{ width }} src="/dog.jpg" /> <figcaption>My width is {width}px</figcaption> </figure> )} </Measure> {/* some JSX below */} )
With Render props we are composing with the logic we want to reuse, just for the components that are interested in that logic. In the previous example, only the image is composed with the Mesure logic. The HoC approach is composing the entire set of components with the mesure logic.
Render Props is defined inside a method that is rendered, this means composition with Render Props happens at render time, not at run time like HoCs. This feature gives composition via Render Props access to props out of the box, which is very powerful. In HoC to get access to props you need to implement some code yourself to handle that case. You can see an example in connect from react-redux with the called ‘ownProps’.
Before Hooks, composition in React happened only vertically (bottom-up) between components in the tree. Heads up, I'm specifically talking about composition in the component tree and not data flow. Data flow in the component tree is top-down
Hooks allows composition perpendicular to the tree. - Sebastian Markbåge (author of the Hooks proposal)
Composition perpendicular to the tree means that now we can reuse component logic inside different components. This is genius.
I find brilliant the atom and electron analogy that Dan Abramov used to describe React Components and React Hooks.
When scientists discovered the atom they thought they were the smallest thing we are going to find. But later they discover the electron, which is a smaller part inside the atom. It turns out that the electrons explain a lot about how atoms work. - Dan Abramov.
There are countless advantages of using Hooks compared to HoC and Render Props. Here are some that I find very relevant for the subject of this article:
If you are excited about composition (you probably should) and you want to learn more about it, I recommend you watching my video about composition
The principles behind composition and inheritance in React don’t differ from composition and inheritance in general software development terms. That being said, there are some small considerations in React, for instance the bundle size which would not matter much in a server-side environment.
Inheritance is a rigid, tightly-coupled approach. Every ancestor in the hierarchy adds a coupling layer.
When we reuse a use case of a given class by inheriting from it, we also bring all the implicit code from the ancestors, even the code from the use cases we don’t use. In JavaScript that means more code to bundle. This extra code is also more difficult to optimize and reduce the size by for instance reducing the number of characters of method names or class properties.
Due to tight coupling, changes to the base class could potentially break any of the descendant classes. The probability of breaking changes increases when you extend a class implemented by a third-party author. That’s the reason you should never extend a class that extends React.Component; your component extends React.Component and inheritance should stop there.
We won't spam you as per our Privacy Policy.
Share this on:
Comments? Shoot me a tweet @alex_lobera !
GraphQL Evening with Round Table 💥 Online
London, UK
Prices & more details