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.
LinkedStateMixin is an easy way to express two-way binding with React. In React, data flows one way: from owner to child. We think that this makes your app's code easier to understand. You can think of it as “one-way data binding.”
Two-way binding gives components in your application a way to share data. Use two-way binding to listen for events and update values simultaneously between parent and child components.
In one-way data binding information flows in only one direction, and is when the information is displayed, but not updated. In two-way data binding information flows in both directions, and is used in situations where the information needs to be updated. They each have their uses, but most applications use both.
Data Binding is the process of connecting the view element or user interface, with the data which populates it. In ReactJS, components are rendered to the user interface and the component's logic contains the data to be displayed in the view(UI).
Starting from the first part of your question, there are two primary reasons for react not going with two way data binding:
In React, we can share state among different child components by lifting the state up to a common parent component. When a shared piece of state is updated, all the child components can update themselves. Here is a good example from the docs related to forms.
Talking about the performance benefits, two way data binding in some other context (say AngularJS) works by means of watchers watching different elements. This sounds easier (and less code than React's one way data flow) for a small number of elements, however as the number of your UI components/elements grow, so does the number of watchers. A single change in this case causes a lot of watchers to fire up in order to keep things in sync. This makes the performance a bit sluggish. In case of React, since there is only one way data flow, it's easier to determine which components need to be updated.
Coming to the second part of your question, your state library provides the data to your form components causing any dependent components to update on state change, sweet. Here are my thoughts:
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 store update in itself will happen pretty quick. JavaScript runs very fast, it's the DOM updates which often times causes bottlenecks. So, unless there are hundreds of dependent form elements on the same page and all of them are getting updated, you'll be just fine.
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?
I don't think it'll have significant difference.
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 negligibly) with some fields and sometimes slower with others.
Agreed with this.
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.
I think most of the use cases would be solved with a simple input
. Again, I don't see a performance benefit with fewer state updates here. Debounce could be useful if for example I'm running an API call on the input (and want to wait before the user stops typing).
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 for the entire form (until submit)?
I don't see a benefit in ignoring changes for a particular field or waiting until submit. On the other hand, when using forms, a common use case I come across implementing things is data validation. For example,
These cases would need the state to be updated as the user is typing.
You should be fine with updating state as the user is typing. If you're still concerned about performance, I would suggest to profile your components to isolate bottlenecks if any :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With