Crafting more reusable react components

Nick Redmark
The Ideal System
Published in
3 min readMar 18, 2017

React UI component libraries are springing up everywhere. Here’s how we could improve their reusability.

Atomic design

To be more precise, let’s first make a distinction.

If you don’t yet know about atomic design check it out. The idea is that you should design bottom up:

  • starting from so-called atoms (like labels, inputs and buttons),
  • combining them into molecules (a form field, a user badge),
  • which can then be used to create organisms (a header, a list of posts),
  • and finally full pages.

Of course, react’s modular structure lends itself perfectly to such an approach.

Low vs high level presentational components

Let’s now make a more binary distinction:

  • Low level components (atoms), where the DOM-specific details are handled (such as which built-in components to use, refs, DOM-events handling, styling)
  • High level components (molecules, organisms, pages), that combine low and high level components

Please note that this is not the common dumb vs smart component distinction. Assume that all the components I’m talking about here are dumb, or presentational.

The intrinsic value of high level components

Take a look at the following login form:

import {Form, EmailInput, PasswordInput, PrimaryButton} from '../atoms'class LoginForm extends React.Component {
constructor() {
this.state = {
email: '',
password: ''
}
}
onSubmit = () => {
this.props.onLogin(this.state.email, this.state.password)
}
render() {
return <Form onSubmit={this.onSubmit}>
<EmailInput
label="Email"
onSet={email => this.setState({email})}
/>
<PasswordInput
label="Password"
onSet={password => this.setState({password})}
/>
<PrimaryButton
onClick={this.onSubmit}
>
Log in
</PrimaryButton>
</Form>
}
}

Here are some questions: is it react or react native? How is it styled (bootstrap, material UI, some css-in-js solution)? What’s the implementation of the low level components (such as EmailInput and PrimaryButton)? Is it compatible with other components you plan to use?

Usually, questions like these would all be relevant for you to decide whether you can use this component, and the answer would be “it depends”. It would depend on the implementation of the low level components. And it’s a shame. Instead, the answer should be “it doesn’t matter”. You should be able to pick this component for your own usage (assuming you like it) regardless of the implementation of the low level components.

Meta UI components

So here’s my proposal for a simple pattern: whenever you design UI components for reuse,

  1. separate the low from the high level components
  2. make the high level components independent from the low ones

Point 2 is an instance of the dependency inversion principle: one should “depend upon abstractions, [not] concretions.”

A simple solution could be wrapping the high level components in a HOC that takes all the lower level components as arguments.

export default ({Form, EmailInput, PasswordInput, PrimaryButton}) => {
return class extends React.Component {
constructor() {
this.state = {
email: '',
password: ''
}
}
onSubmit = () => {
this.props.onLogin(this.state.email, this.state.password)
}
render() {
return <Form onSubmit={this.onSubmit}>
<EmailInput
label="Email"
onSet={email => this.setState({email})}
/>
<PasswordInput
label="Password"
onSet={password => this.setState({password})}
/>
<PrimaryButton
onClick={this.onSubmit}
>
Log in
</PrimaryButton>
</Form>
}
}
}

You can then create a specific LoginForm component by providing concrete implementations of those low level components.

import {Form, EmailInput, PasswordInput, PrimaryButton} from './atoms'
import {loginForm} from './molecules'
const LoginForm = loginForm({Form, EmailInput, PasswordInput, PrimaryButton})

Note that this specific pattern requires all concretions to be constructed in the correct order, and because of this it wouldn’t support circular dependencies (one could make the case that that’s bad design anyway). A more sophisticated approach would be to use some inversion-of-control (IoC) library such as inversify.

About me

I think out loud about web engineering. If you want more of it, follow me here and/or on twitter.

--

--