A common question of newcomers to React is why two-way data binding is not a built-in feature, and the usual response includes an explanation of unidirectional data flow along with the idea that two-way data binding is not always desirable for performance reasons. It's the second point that I'd like to understand in more detail.
I am currently working on a form library for apollo-link-state (a new client-side state management tool from Apollo). The concept is very similar to redux-form except using apollo-link-state instead of redux as the state manager. (Note that form state is stored separately from the state of domain entities, although an entity can optionally be used to populate the initial state of a form.)
When the user makes changes on the form, the library immediately updates the store via onChange
handlers. I was thinking about allowing individual fields to opt-out of that behavior in case the programmer was concerned about performance, but then I started wondering when this would ever be a real performance issue. The browser is going to fire the oninput
event no matter what, so the only performance consideration I can think of is whether or not the store is updated as the user types. Granted there is the additional overhead of executing a mutation rather than just calling setState()
, but that essentially just amounts to a couple additional function calls. And let's suppose that I weren't using apollo but just calling a function that updates some global store directly - what would be the performance consideration then?
My thinking is that if a form is going to support immediately updating the form state as the user types in one field, it might as well do so for all the fields. The user can only type in one field at a time, and I don't see the benefit of making the page sometimes faster (probably negligible) with some fields and sometimes slower with others. Furthermore, my library allows consumers to use whatever input components they want, so if the programmer just wants fewer state updates, they could just write a component that debounces React's onChange
event or uses the browser's own change
or blur
event instead.
Am I missing something here? Is there some other reason why a user of my library would want to ignore changes for particular fields until the user submits the form? Or maybe a more useful option would be to ignore changes to the entire form (until submit)?
Here's a basic (greatly simplified) illustration of the basic concept behind my current approach:
// defined in a globally-accessible module
const formState = {
// This somehow causes any dependent form components to re-render
// when state changes
update(formName, updatedState) {
...
}
}
export default formState
...
// UserForm.js:
export default class UserForm extends PureComponent {
componentDidMount() {
formState.userForm = {
firstName: '',
lastName: '',
}
}
handleChange(e) {
const { target } = e
formState.update('userForm', { [target.name]: target.value })
}
//...
render() {
const { userForm } = formState
return (
<form onSubmit={this.handleSubmit}>
<label for="name">Name</label>
<input id="name" type="text" onChange={this.handleChange} value={userForm.name} />
<label for="email">Email</label>
<input id="email" type="email" onChange={this.handleChange} value={userForm.email} />
</form>
)
}
}
Finally, for the sake of completeness, I should mention that there are some API design considerations involved in this as well. Individual input components could have a slightly simpler design if I did not provide an option to opt-out of the automatic 2-way binding. I can post details if anyone is interested.
onChange
handler in order to notify the Form when it's updated (via a callback obtained fromthis.props
) - but they are very simple and there's just onehandleChange
method on the Form that handles all the state updates. – Grath