DRY React

← back to the blog

HTML is a declarative language that offers no opportunity for code reuse. When a page contains several elements that are identical or almost identical, the HTML needed to create those elements is simply repeated. Programming languages - whether they be Turing-complete languages like PHP or "templating" languages like Handlebars - provide a way to write a component once and reuse it: but the challenge is knowing how to abstract it. The ways in which a "partial" can be reused are generally limited to whatever arguments are defined by the partial.

React is much more than a templating language, and the breadth of ways in which components can be reused greatly exceeds languages like Handlebars. Some of these patterns are more useful than others, however. In this post I'll provide a quick summary of the ways in which React components can be reused and the situations in which the pattern should be employed (if any).

Mixins

Mixins were originally a way of reusing the same function between React components. The pattern is simple: the component importing mixins at definition that can subscribe to a data store, provide inline styles or really do anything that a React component method can do. This pattern has been deprecated since React 0.13 and you can read about the reason's on Facebook's blog. The tl;dr is that changes in the mixin always have consequences for the components that use it, including name clashes and side-effects. For the reasons given there, I don't recommend it.

Inheritance

Inheritance is a classical object-oriented pattern of code reuse, and because React components are "classes" (kind of) you are free to use this. Suppose you are creating a set of form elements:

import React, {Component} from 'react'

class TextInput extends Component {
    constructor(props) {
        super(props)
        this.state = {
           value: ''
        }
    }

    _updateValue(e) {
        this.setState({value: e.target.value})
    }

    render() {
        return <input type="text" value={this.state.value} />
    }
}

class EmailInput extends TextInput {
    render() {
        return <input type="email" value={{this.state.email}} />
    }
}

The EmailInput component gains some benefits: it doesn't have to repeat the annoying code to update the state on when text is entered in the form. The downsides of this approach are even worse than they are for mixins, however. You might say that inheritance has all the same problems that mixins do, but on steroids, since several methods can be inherited at once. Worse yet, the parent component may be directly used to create elements, increasing its responsibility and the number of things that can go wrong in a refactor. In short, inheritance and class introduce a host of problems in general, and like mixins, are not recommended by the creators of React.

Subcomponents

React components pass props from parents to children, and this can be exploited to do simple remixing. Taking the example above, we could make the Input component agnostic as to it's type:

import React, {Component} from 'react'

class Input extends Component {
    constructor(props) {
        super(props)
        this.state = {
           value: ''
        }
        this._updateValue = this._updateValue.bind(this)
    }

    _updateValue(e) {
        this.setState({value: e.target.value})
    }

    render() {
        return <input type={this.props.type || 'text'} value={this.state.value} />
    }
}

const EmailInput = props => {
    return <Input type="email" {...props} />
}

Even with this simple example, we can see the advantages of subcomponents. EmailInput can change from being class-based to functional, making it easier to understand. Everything EmailInput provides to Input is clearly laid out in its prop declarations. Since EmailInput doesn't have any methods, there isn't any confusion about responsibility: EmailInput is just a special case of Input.

It can become tempting to overuse this and create special cases of special cases. There are two downsides to this approach. One is that it has the potential to create an awful lot of nesting. This nesting increases the complexity of the DOM, the component hierarchy in the debug tools, and in the prop chains. Name conflicts will eventually occur in the prop chains. All of these create headaches when debugging. Another disadvantage is that every single special case is indeed a new component - even if it contains much less code. In this simplified example, you'd need a new component (probably a new file) for EmailInput, PasswordInput, NumberInput, etc..

Subcomponents with children

One thing I love about React components over "partials" in templating languages is the special children property. In a recent PHP project, I wanted to create a reusable modal and ended up having to split to necessary code between two files: one to open the modal's DOM elements and another to close them. This is ugly and I didn't like doing it (but I did it anyway). In React you can actually make this beautiful:

const Modal = props => {
    const display: props.isOpen ? 'block' : 'none'
    return (
        <div className="modal-wrapper" onClick={props.onRequestClose} style={{display}}>
            <div className="modal">
                {props.children}
            </div>
        </div>
    )
}

(I'll leave out all of the CSS you need to make the modal behave properly).

We can put literally anything inside it as long we remember to pass the isOpen and onRequestClose methods (which we would always carefully document with PropTypes, right?).

Subcomponents with functions as children

We have to remember to pass those props because React is like a boy band: One Direction (of information flow). Since a modal can't open itself, there must be some controller component sitting at a level above both the modal and the thing opening it. That controller (or a redux store) keeps track of whether the modal is open and passes that information down as props. Is there a way that we can abstract state control, instead of markup?

class ModalController extends Component {
    constructor(props) {
        super(props)
        this.state = {
            open: false
        }
        this._toggleOpen = this._toggleOpen.bind(this)
    }

    _toggleOpen() {
        this.setState({open: !this.state.open})
    }
}

But what should the render method be? The child pattern on its own isn't useful because ModalController has no opporunity to send props to its children.

// This won't work:
<div>
    {this.props.children // but no way to give it _toggleOpen}
    <Modal open={this.state.open} />
</div>

The solution is the function-as-child pattern. Somewhat unexpectedly, you can pass a function instead of an component as the child of another component. Our ModalController render method is:

render() {
    return this.props.children(this.state.open, this._toggleOpen)
}

and we use it like this:

<ModalController>
    {(open, toggle) => {
        const display: props.isOpen ? 'block' : 'none'
        return (
            <div>    
                <button type="button" onClick={toggle}>Open Modal</button>
                <Modal open={open}>
                    {// modal interior}
                </Modal>
             </div>
        )
    }}
</ModalController>

Of course, we now need two different components to make our Modal work properly.

Higher-order components

A higher-order function is simply a function that returns another function, a powerful abstraction pattern that is the heart of functional programming. In React, a component is a function that returns an element (or a class with a render function that returns an element), but there is no reason the chain has to stop there. We can have functions returning components.

Here is what it looks like:

const HOC = (args) => props => {
    // regular functional component
}

const HOC = (args) => class extends Component {
    // regular class component
}

In each case, the inner components have access to all the normal props and state, as well as the arguments, which are provided at the time the component is created rather than the time the element is created. The React-Redux connect function is a higher order component - it produces a React component but the user of connect doesn't write any JSX at all to do it.

Any of the earlier examples in this tutorial could be reworked as HOCs. But let's take our Modal example again to illustrate the concept. It would be really great to combine ModalController and Modal into a single reusable component, while leaving both the button and the modal content up to the consuming code. Here is one solution using HOCs:

const HOC = (Button) => class extends Component {
    constructor(props) {
        super(props)
        this.state = {
            open: false
        }
        this._toggleOpen = this._toggleOpen.bind(this)
    }

    _toggleOpen() {
        this.setState({open: !this.state.open})
    }

    render() {
        const display: props.isOpen ? 'block' : 'none'
        return (
            <div>
                <Button type="button" onClick={this._toggleOpen} {...this.props} />
                <div className="modal-wrapper" onClick={props.onRequestClose} style={{display}}>
                    <div className="modal">
                        {props.children}
                    </div>
                </div>
            </div>
        )
    }
}

We can use it like this:

const ButtonWithModal = HOC(<AnyButtonComponent>)
<ButtonWithModal>
    <ModalContent />
</ButtonWithModal>

which gives us the best of both worlds.

Conclusion

HOCs are probably the most powerful pattern you can employ to reuse code, but they can often be overkill. The function-as-child pattern is quite underutilised in the React community, and the choice of whether to use a function-as-child or a HOC pattern can be a difficult one. And of course, subcomponents are simple to grasp and simple to use, and may often be the best choice for a particular problem.