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.
context
for Global ConfigurationReact 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:
ReformContext
’s at different branches.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)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:
onChange
, onFocus
and onBlur
need to be wired up.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.
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.
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 :)