Function composition, in mathematics, is the application of a function over the
value of another one to produce a third function. Given f(x)
and g(x)
,
ƒ: x → f(g(x))
is the composition of f
with g
.
A more simple example is the composition of add2: x → x + 2
with
add4: x → x + 4
. It is a function ƒ
that one can label as add6
since
add6: num → add2(add4(num))
. The application of add6
with 4
can be
represented as: add6(4) = add2(add4(4)) = add2(8) = 10
.
JavaScript functions are not pure mathematical functions, but you can also compose them. We do this all the time. If we express our components as functions, we can also compose them.
We are very used to nest HTML elements:
+-<body>----------------------------+
| +-<div>-------------------------+ |
| | <span> | |
| | Continue? | |
| | | |
| | <button> <button> | |
| | +--------+ +--------+ | |
| | | Cancel | | Ok | | |
| | +--------+ +--------+ | |
| +-------------------------------+ |
+-----------------------------------+
With components as functions, we can build much more than just nested HTML elements. Structure, behavior, and styles can be composed to create user interfaces.
React was one of the first mainstream JavaScript libraries to propose a simple way to compose components. You can represent the interface above using React.
<SectionBox>
<Modal>
<InfoMessage>Continue?</InfoMessage>
<Button>Cancel</Button>
<Button>Ok</Button>
</Modal>
</SectionBox>
Each component (<SectionBox>
, <Modal>
, <InfoMessage>
, <Button>
) brings
its own custom behavior. In my opinion, being able to compose components easily
is the killer feature of React.
An icon and a button
I have to implement something like this in almost every project I build:
+---------------+
| Add (+) |
+---------------+
This button is a simple example of how to compose different components to build
another. We can name this component IconButton
. It receives icon
,
children
, and onClick
as props. A simple implementation would be like:
let IconButton = ({icon, children, onClick}) =>
<Button onClick={onClick}>
{children}
<Icon icon={icon} />
</Button>
To build IconButton
, we used Button
and Icon
components. As simple as it
looks like, this component shows us how we can abstract something by composing two
components together. Button
could have a very complicated logic to decide when
to call the onClick
callback, and that would be invisible to IconButton
.
A button and a tooltip
Some complex UIs need to show some tooltip hints to the user while they use them, guiding users through the available features. In some project I had to implement a component very similar to this:
+--------------------+
| |
| Hint text |
| |
+---------V----------+
+--------------+
| Button |
+--------------+
This component isn’t that complex. The Button
is the same component we used before.
Tooltip
is a new component that receives children
, direction
and hidden
to render a floating tooltip.
let Tooltip = ({direction, hidden, children}) =>
<div className={`Tooltip is-${direction} ${hidden ? 'is-hidden' : ''}`}>
{children}
</div>
Tooltip.propTypes = {
direction: React.PropTypes.oneOf(['up', 'down'])
}
A component using both could look like this:
class ActionWrapper extends Component {
constructor (props) {
super(props)
this.state = {isTipOpen: false}
}
onButtonClick () {
this.setState({isTipOpen: !this.state.isTipOpen})
}
render () {
return (
<div>
<Tooltip direction='up' hidden={!this.state.isTipOpen} />
<Button onClick={onButtonClick.bind(this)}>Button</Button>
</div>
)
}
}
The onButtonClick
method hides/shows the Tooltip
. Clicking the button
calls this method. This type of component is simple too.
However, one little requirement can change everything: Tooltip
had to prevent
overflowing the screen when opened. If the button is rendered too high on the
screen, the tooltip couldn’t be hidden and instead open with direction='down'
.
Normal Automatic
+--------------------+
| |
| Hint text |
===========================screen==========================
| |
+---------V----------+
+--------------+ +--------------+
| Button | | Button |
+--------------+ +--------------+
+---------ʌ----------+
| |
| Hint text |
| |
+--------------------+
It’s not good to implement this logic inside each component that uses Tooltip
.
We have to abstract this automatic positioning logic inside Tooltip
and reuse
it everywhere.
As we can compose other components with Tooltip
, they get this behavior
for free.
Layout components
The most used components inside React projects I built were the layout components. These components abstract the logic and the styling of how to compose other components to create an entire UI.
If we use component composition with the right components, all pages can be just the composition of a bunch of components and have no page-specific styling/logic.
In my experience, decoupled components can dramatically improve the flexibility of the project. After implementing some sections, designers will ask for changes. Product managers and clients will want some new or custom feature. If we focus on having a simple and flexible kit of components, changing the project won’t be hard.
Spacing
is a component I used in many projects. It is responsible for wrapping
every child inside a div
and space them accordingly.
<Spacing>
<Button>Ok</Button>
<Button>Cancel</Button>
</Spacing>
This piece of code would render two buttons separated by a blank spacing.
Spacing
s can also be nested to create more complex layouts.
<Spacing>
<span>Continue?</span>
<Spacing direction='horizontal'>
<Button>Cancel</Button>
<Button>Ok</Button>
</Spacing>
</Spacing>
+-<Spacing>----------------------+
| Continue? |
| |
| +-<Spacing>------------------+ |
| |+--------+ +--------+| |
| || Cancel | | Ok || |
| |+--------+ +--------+| |
| +----------------------------+ |
+--------------------------------+
Complex pages could compose many Spacing
s to implement their layouts in
a reusable and straightforward manner.
Spacing
’s implementation is pretty simple:
let Spacing = ({children = [], direction = 'vertical'}) =>
<div className={`Spacing is-${direction}`}>
{[].concat(children).map((child) =>
<div className='Spacing-item'>{child}</div>
)}
</div>
Its styles are simple too:
.Spacing {
display: flex;
}
.Spacing.is-vertical {
flex-direction: column;
}
.Spacing.is-horizontal {
flex-direction: row;
}
.Spacing-item {
padding: 5px;
}
For more complex layouts, I recommend Reflexbox. It is a kit with great components to build designs using Flexbox.
Flexible components
The last example I want to show you is the idea of flexible components. From my experience, user interfaces change all the time. As UI developers, we need to improve our projects to embrace change.
Very strict components do not match with flexible projects. We end up creating and using them a lot.
A simple and strict way of implementing a modal component would be like:
<Modal
title='Continue?'
message='This will remove all changes'
onOk={}
onCancel={} />
It isn’t possible to change which buttons render, insert a component between the title and the content and so on.
By splitting the Modal
component into many components, we can provide an easy
way of changing the way modals look.
<Modal>
<ModalTitle>Continue?</ModalTitle>
<ModalMessage>
This will remove all changes
</ModalMessage>
<ModalActions>
<Button onClick={}>Ok</Button>
<Button onClick={}>Cancel</Button>
</ModalActions>
</Modal>
Splitting components give us the flexibility to change how each modal in our project renders. It is simpler to change the buttons, content placement and even change the modal style. An excelent example of the power that component composability gives to us.
You can even create an abstraction of a standard modal and leave the building blocks open for reusability if needed.
These are just some patterns and ideas I used in the projects I built in the last year using React and other libraries. Feel free to contact me and send any improvements tips to this article.