Lessons Learnt Rewriting a React Library from Scratch

Back to overview
January 23, 2017
Saying that Daniel loves programming would be an understatement. He has a quasi-mystical relationship with it. Maybe it is more accurate to say that programming loves him? When he's not wrangling code for Codecks or participating in hackathons, he humbles his opponents on Age of Empires II (1v6! You monster).
Codecks LogoCodecks is a project management tool inspired by collectible card games.
Sounds interesting? Check out our homepage for more information.

About one and a half years ago I started my work on React Reform because I wasn’t able to find an existing form library offering a good compromise between customizability and writing as little boilerplate code as possible. Have a look at the Introduction to React Reform Post to see how form themes are a great vehicle for abstracting behaviour and style for your forms. This way defining the contents and validation constraints for specific forms can be kept to a minimum.

I’ve been using React Reform for production use at Codecks early on, but as time has passed and the amount of features continued to grow, some things within the library started to feel not quite right.

Since the code base wasn’t that massive with about 500 lines of code, I decided to start from scratch. My main goals were to arrive at an easier to understand api for library users as well as following some of the best practices I’ve adapted to in the past year.

So let’s dive in.

1. Use React’s context for Global Configuration

React Reform relies on global configuration to make themes and validations easily accessible.

The old way of doing this looked like this:

const themes = {};

export function registerTheme(name, theme) {
  themes[name] = theme;
}

export function getTheme(name) {
  return themes[name];
}

I.e. creating a global register within the library module. This isn’t without disadvantages. Especially in the context of server-side-rendering.

When rewriting React Reform, I chose a path much closer to how a lot of other react libraries solve this issue: by creating a component that’s supposed to sit at the top of your app. You pass the configuration as props to this component and all it’s doing is to create a context that all your forms have access to.

So here’s the new code in a slightly simplified version:

import React from 'react'

export default class ReformContext extends React.Component {

  static childContextTypes = {
    reformRoot: React.PropTypes.object.isRequired
  }

  getChildContext() {
    return {
      reformRoot: {
        getTheme: (name) => this.props.themes[name],
        getValidation: (name) => this.props.validations[name],
      }
    }
  }

  render() {
    return this.props.children
  }
}

You apply it like this:

class App extends Component {

  render() {
    return (
      <ReformContext themes={themes} validations={validations}>
        <div>
          ...Your App Code...
        </div>
      </ReformContext>
    )
  }
}

This approach comes with some benefits:

  • Server Side Rendering works out of the box since we’re not storing anything in some global register but only within the tree we’re rendering right now.
  • Granularity: if you know that you’re going to use different types of default themes within different parts of of your application, you’re free to go further down your component tree and create several ReformContext’s at different branches.
  • Composability: Should you want to overwrite some configuration settings for specific branches of your tree, it’s quite straight forward to create a ReformOverwriteContext component that takes the existing configuration, merges the changes, and pass it down to its subtree. (Note that this particular feature is not implemented in React Reform yet)

2. Embrace Components as much as Possible

A very critical element for React Reform is wrapping input components to be able to interact with the surrounding form. There are many things to consider:

  • the model’s data needs to be passed down
  • onChange, onFocus and onBlur need to be wired up.
  • we need to be able to call focus on the input in case validation failed and we want to focus the first failed field.

The goal is to offer a mechanism flexible enough to allow being compatible with a great variety of input components. The old way to do this was to offer a function and passing down a more or less complex option parameter hoping to cover most of the complexity:

import {DateField} from 'react-date-picker'
import {wrapInput} from 'react-reform'

wrapInput("DatePicker", DateField, {
  valueToProps: value => ({date: value || null})
  propNameForOnChange: "onUpdate"
})

This approach had its limitations though. Especially with more complex components. So let’s see how to implement the DatePicker example above using the new api:

import {DateField} from 'react-date-picker'
import {WrapInput} from 'react-reform'

const DatePicker = props => (
  <WrapInput type="DatePicker" directProps={props}>{
    ({value, {onChange, ...rest}) => (
      <DateField date={value || null} onUpdate={onChange} {...rest}/>
    )
  }</WrapInput>
)

Well, it certainly got a bit more involved. However as you may see it’s “just” components now. The DatePicker is defined as a component containing a WrapInput which is able to communicate with the surrounding form. It then passes this information down to its children via a function callback. This allows to pass the appropriate props to the target component.

Since we’re operating with components, we have a much increased flexibility to do things here. For example, we could intercept the communication between React Reform and the raw input component, or prevent certain props from being reached down.
Furthermore it’s perfectly thinkable to not just return a single component but something more complex, attaching the correct listeners (contained within the themeProps), to the correct corresponding element.

Let’s look at an example. It’s certainly no code you’d want to put to production, but it should give a very good idea of what’s possible by being able to fully embrace components:

import {WrapInput} from 'react-reform'

const DatePicker = props => (
  <WrapInput type="DatePicker" directProps={props}>{
    ({value, themeProps: {onChange, onFocus, onBlur, ref, ...remainingThemeProps}}) => (
      <div {...remainingThemeProps}>

        <input type="number" value={(value && value.day) || ''}
          onChange={e => onChange({...value, day: e.target.value})}
          onFocus={onFocus} onBlur={onBlur} ref={ref}
          placeholder="day" style={{width: '3em'}}
        />

        <select value={(value && value.month) || ''}
          onChange={e => onChange({...value, month: e.target.value})}
          onFocus={onFocus} onBlur={onBlur}
        >
          {'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'.split(' ').map((m, i) => (
            <option value={i + 1} key={i}>{m}</option>
          ))}
        </select>

        <input type="number" value={(value && value.year) || ''}
          onChange={e => onChange({...value, year: e.target.value})}
          onFocus={onFocus} onBlur={onBlur}
          placeholder="year" style={{width: '4em'}}
        />

      </div>
    )
  }</WrapInput>
)

Rather than targeting only one input it’s now possible to target multiple ones, correctly passing the change, focus and blur events to the form. Special reference to the ref parameter which determines which field should get focussed if there are validation errors for this field. Note that you can overwrite the default focus behaviour of a component. See the docs for more details and examples.

Recognising the Limits

Sometimes working with components in an unusual fashion can make your api harder to understand. This motivated another change for the rewrite. Namely how themes are defined.

Before, you had to write code like this:

const myTheme = (FormContainer, Fields, opts) => (
  <FormContainer>
    //...formHeader
    <Fields>
      {(Field, fieldOpts) => (
        <div>
          // label, validation messages, etc...
          <Field/>
        </div>
      )}
    </Fields>
    //...form footer
  </FormContainer>
)

Yes, components all the way. But this introduced more complexity than necessary: a lot of people had issues understanding what this Fields parameter/component entailed and what it’s child-as-a-function implied.

So I decided to go with a more classical approach:

import {createTheme} from 'react-reform'

const myTheme = createTheme({
  renderForm: (FormContainer, children, opts) => (
    <FormContainer>
      //...formHeader
      {children}
      //...form footer
    </FormContainer>
 ),

  renderField: (Field, fieldOpts) => (
    <div>
      // label, validation messages, etc
      <Field/>
    </div>
  )
})

It’s just as powerful but presumably much easier to understand and work with.

3. Writing Docs is a Great Opportunity to Review all your User-Facing Design Decisions

One major goal of the 1.0 was to have a proper web page for the documentation. While I’ve put quite some effort into the old readme, having a dedicated page with a nice navigation and interactive examples promised to be much more effective. This also was a nice excuse to experiment with a neat approach of generating static html via React.

When setting out to write the docs, I was constantly exposed to all my design decisions. This turned out to be a very powerful tool. While I tried to explain how users can interface with the code, I was always wondering whether there wasn’t be a simpler way to do certain things. And indeed in some cases, some simplifications could be made by approaching the code from the user’s perspective.

Another side effect of writing docs is to think hard about your vocabulary. Is it form inputs or fields? Validations or Validators? Which terms are exposed in the code? And are they consistent? It turns out that I was able to find and fix some issues here.

One more thing I was keen on doing was to provide some interactive examples throughout the docs. This serves as a great smoke test for your library, while also forcing you to keep your documentation up to date. Especially the section showcasing how to wrap various kinds of datepickers forced me to stress test the flexibility of the WrapInput component. After I managed to fully wrap even the react-bootstrap-daterangepicker which itself is just a wrapper around the jquery-based bootstrap-daterangepicker, I was optimistic that React Reform would probably hold up quite well with a variety of components.

After finishing the documentation which in fact took longer than rewriting the whole library, I couldn’t help but notice that the readers weren’t the only ones who benefitted. I as a library author learned a lot of valuable lessons helping to improve the library further by explaining it in detail.

Alright, time to wrap up. I hope, I could give you insights to some patterns that may help you to improve your code. Until next time! (Where I’ll likely talk about the benefits of mixing tachyons and react.)

If you found this post interesting, you might also want to follow me on twitter so I can let you know about new posts :)

So, what is Codecks?

Codecks is a project management tool inspired by collectible card games.
Learn more
Want to stay in touch?

Don't feel like trying out Codecks right now? Don't worry! Leave us your email so we can keep you posted about new features.