Writing Clean and Concise React Components by Making Full Use of ES6/7 Features and the Container-Component Pattern
The Story So Far
If, like myself, you’ve ever created a website using the “standard” tools of plain old HTML or templates, preprocessed CSS, and JavaScript, then you may also have shared the same delight I did when React came along and offered a way to easily break a website or app into manageable, reusable chunks.
This also came at a time when JavaScript was maturing immensely, going from what some considered a “toy language” to one of the most popular languages out there today. After promises came on the scene we could forget about writing sideways pyramids and getting ourselves into callback hell. Then along came async/await, and now most of us try to avoid those “messy and confusing” promise chains wherever possible. We are spoiled, literally, for choice.
Then along came Flux, after that Redux. State management just got a whole lot easier, and although you might not always need it, Redux is often the go-to state management tool for a lot of developers. That little extra initial setup often goes a long way and saves us major headaches further down the line.
As the language evolves so fast it’s hard to keep track of all the new features, so this article is about my personal opinion on how to make the best use of them to improve your components without sacrificing anything whatsoever.
I should point out here that in all my examples I’ll be using React Native, but the principles are exactly the same in React… So let’s get into it!
Container Component Pattern
A remarkably simple pattern that enables you to make full use of React’s composability, which can be simply explained as your container handles the functionality and renders your component, while your component is concerned with displaying the data fetched by your container.
Here’s a container:
import MyComponent from '../MyComponent'
export default class MyContainer extends Component {
constructor (props) {
super(props)
this.state = {
// ...
}
}
someFunction () {
// ...
}
render () {
return (
<MyComponent />
)
}
}
Now I know what you’re thinking, that’s just a React component? Well, you’re right. What’s key here though, is that your container component is fully separated from your stateless component and is only concerned with data, passing anything relevant through to your component to be rendered.
The component should just be a stateless functional component. If you haven’t heard the term before - it’s exactly what it sounds like: a component that is a function and has no state. Here’s a simple example:
import Button from '../Button'
import style from './style'
const MyComponent = ({ headerText, buttons }) => (
<View style={style.container}>
<View style={style.headerWrapper}>
<Text style={style.headerText}>
{headerText}
</Text>
</View>
<View style={style.contentWrapper}>
{buttons.map(button => (
<Button {...button} />
)}
</View>
</View>
)
An oft-overlooked benefit is the double PropType checks that this pattern essentially enforces. When raw props are passed to your container, the ones that you manipulate will be flagged by your linter and, unless you enjoy squiggly red lines, you add them to your PropType checks. For the rest of the props, the ones you know exist but your container has no business handling, you should spread them into your component. You can see this in the above code snippet, where the button object is spread into the Button component.
The second layer of safety this pattern offers comes from your component’s PropType checks, your first layer of defence is your container’s PropType checks, which simply check the values passed are what you expected. Your component can then check that the props were manipulated properly inside your container, without this pattern this second check would not be possible. This is a little extra setup for a lot less headaches as your app grows.
For a more in-depth explanation of the pattern, check this article out.
Helpful ES6/7 Features
In this part of the article I’ll be talking about how to make use of the latest features from the ECMAScript standard that can help make your life easier and your code easier on the eyes.
Class Properties
One of the simplest of the new ES features is also one of my favourite. This one simple feature allows you to very often do away with the constructor method in your classes. So what is it? Put simply, class properties allow defining a class property outside any of your class’ methods.
To show you just how beneficial this one simple feature can be, here’s a component that doesn’t use class properties:
export default class MyContainer extends Component {
contructor (props) {
super(props)
this.classPropertyA = 'something'
this.classPropertyB = 123
this.state = {
stateItemA: undefined,
stateItemB: 'default string'
}
this.someMethod = this.someMethod.bind(this)
this.anotherMethod = this.anotherMethod.bind(this)
}
someMethod () {
// ...
}
anotherMethod () {
// ...
}
render () {
return (
<SomeComponent />
)
}
}
Yikes
export default class MyContainer extends Component {
classPropertyA = 'something'
classPropertyB = 123
state = {
stateItemA: undefined,
stateItemB: 'default string'
}
someMethod = () => {
// ...
}
anotherMethod = () => {
// ...
}
render () {
return (
<SomeComponent />
)
}
And here’s the exact same component, but with class properties:
By combining this feature with other, more well-known new ES features like arrow functions, which don’t open up a new context, we have managed to cut a third of the lines out of our container file. We have no need for a constructor anymore, nor do we need to bind the context of our class methods to the class’ context either.
I will also use this space to explain a pet peeve of mine: people calling super on props unnecessarily. You don’t need to call ‘super(props)’, unless you need to use ‘this.props’ inside the constructor. A React component will always bind ‘props’ to ‘this’, it just happens after the constructor in the component lifecycle.
Static Methods and Properties
Following on nicely from class properties are static methods and properties. Using static methods you can use parts of a class without having to instantiate an entirely new instance of said class. They are called by referencing the un-instanced class followed by the property name, if you use static on a method however this will of course exclude you from accessing ‘this’ inside that method, since the class has not been instantiated.
How does this benefit React components? Well, aside from clearly indicating to anybody reading your code that ‘this’ will not be used, you can also use it to bring propTypes and defaultProps inside the class body. This one is purely personal preference, but in my opinion it looks quite a bit nicer than trailing your PropType checks after your class is defined. Here’s an example:
export default class MyContainer extends Component {
static propTypes = {
someProp: PropTypes.string.isRequired,
anotherProp: PropTypes.number
}
static defaultProps = {
anotherProp: 123
}
...
Decorators
Decorators have been around in other languages for some time. The concept in JavaScript is simple; decorators provide a syntactically-sexy way of wrapping your classes and functions, and adding extra functionality.
The most common use case of decorators with React is using React-Redux’s connect function as a decorator. The concept is simple, so here’s a couple of examples to illustrate how much more elegant they can make your code.
Without decorators:
const mapStateToProps = state => {
const { someStore: { someProperty } } = state
const somePropertyPlusOne = someProperty + 1
return {
somePropertyPlusOne
}
}
class MyContainer extends Component {
// ...
}
export default connect(mapStateToProps)(MyContainer)
With decorators:
@connect(state => {
const { someStore: { someProperty } } = state
const somePropertyPlusOne = someProperty + 1
return {
somePropertyPlusOne
}
})
export default class MyContainer extends Component {
// ...
Since this is just syntactic sugar for wrapping something, they can be chained as many times as you like. In my opinion, this is where decorators really make your code much prettier.
Without decorators:
const mapStateToProps = state => {
const { someStore: { someProperty } } = state
return {
someProperty
}
}
class MyContainer extends Component {
// ...
}
export default reduxForm({
form: 'someForm'
})(connect(mapStateToProps)(MyContainer))
Yo dawg… I heard you like to wrap functions
With decorators:
@reduxForm({ form: 'someForm' })
@connect(state => {
const { someStore: { someProperty } } = state
return {
someProperty
}
})
export default class MyContainer extends Component {
// ...
}
That’s more like it
Object Spread + Generate Props
Object spread is one of the better-known new ES features, so I won’t talk about it too much, I’d rather just show you how I use it. I should mention that the ‘generate props’ method I’m about to show you here was my co-workers idea that I have shamelessly taken and used ever since he first showed me.
So what is object spread? Well, to put it simply, it assigns all the properties of an object onto another object, one by one, at the top level. Think Object.assign. The syntax has become quite ubiquitous now, so whenever you see ‘…’ prefixing a variable inside an object or array, you’re looking at the spread operator. There is array spread, object spread, object rest spread and rest parameters which all use the same ‘…’ syntax. Object rest spread and rest parameters are a little different, however, so I won’t go into those here.
So what is generate props? Well, it’s a great little trick for passing props to a component from a container. It’s nothing groundbreaking, but it does offer a way to clearly see the props you’re passing and do minor manipulations on them before you do so. The idea is simple, have a function that returns all your props, then spread those props into your component to avoid having a big messy lump of JSX inside your render method. Unfortunately, using generate props will not work unless you are using the container-component pattern, as you will see in the examples below.
Using this method won’t necessarily shorten your code, but it does offer some advantages over the traditional method of passing props. Firstly, everything you want to pass as props is spread into generate props, the result of which is spread into your component, so once it’s all linked up, you can freely pass props down chains of components without having to open up the intermediate components. Now, it’s worth mentioning that passing props deeply isn’t recommend, but sometimes it’s unavoidable. Secondly, you have a nice object representation of the props you’re passing, cutting down on JSX and giving you a single point of reference for which props you’re passing in. You’re also free to spread in any other objects, such as your state.
It’s worth noting that this may lead to some redundant props being passed down, however with the advent of functional programming some minor (but unnoticeable) performance hits are expected in order to increase code reuse and readability, amongst other benefits.
Once again, let’s look at an example with, and an example without.
Without generate props:
class MyContainer extends Component {
state = {
itemA: 'something',
itemB: 'another thing'
}
_onPress = () => {
// ...
}
_onPinch = () => {
// ...
}
render () {
return (
<MyComponent
propA={this.props.propA}
propB={this.props.propB}
itemA={this.state.itemA}
itemB={this.state.itemB}
onPress={this._onPress}
onPinch={this._onPinch}
/>
)
}
}
And using generate props:
class MyContainer extends Component {
state = {
itemA: 'something',
itemB: 'another thing'
}
_onPress = () => {
// ...
}
_onPinch = () => {
// ...
}
_generateProps = () => ({
...this.props,
...this.state,
onPress: this._onPress,
onPinch: this._onPinch
})
render () {
const props = this._generateProps()
return (
<MyComponent {...props} />
)
}
}
This one really is personal preference more so than the other things I’ve mentioned in this article.
In Summary
JavaScript and the ecosystem surrounding React is advancing at a rapid pace. This is just an example of how I use some of the new features on offer to make coding more enjoyable, and to cut down on a lot of the boilerplate involved in writing complex applications.