Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Performance implications of implementing 2-way data binding in React

Tags:

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.

like image 277
Matt Browne Avatar asked Feb 22 '18 16:02

Matt Browne


People also ask

Can we have 2 way data binding in React?

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.”

Why we use two 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.

Which is better one way data binding or two way data binding?

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.

What is the role of data binding in React?

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).


1 Answers

2 way data binding implications

Starting from the first part of your question, there are two primary reasons for react not going with two way data binding:

  1. A single source of truth for data changes in a React app, hence less chances of bugs and easier debugging
  2. Performance benefits

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.

Handling state updates

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,

  • provide feedback to the user as and when he is creating a password
  • check if an email is valid
  • perform API calls to see if a username is valid, etc.

These cases would need the state to be updated as the user is typing.

tl;dr

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 :)

like image 152
Divyanshu Maithani Avatar answered Jan 04 '23 19:01

Divyanshu Maithani