Sandro Roth

Two-way Data Binding in React

Published on December 29, 2020, last updated on January 01, 2021

One-way vs. Two-way

With One-way Data Binding the data flows from top to bottom (from the parent component to the child) but not back to the parent - just in one direction. With Two-way Data Binding the data flows in both directions, from top to bottom and from bottom to top (parent to child and vice versa). The child component can mutate the data.

While the AngularJS library (which is out of date) provided real Two-way Data Binding, it is neither supported by Angular 2+ nor other popular frameworks like Vue and React.

Instead we use events: when a child component wants to manipulate the data received from the parent, it emits an event providing the new data. The parent component subscribes to that event and updates the state. The child receives the new data and updates the UI.

Two-way Data Binding vs. One-way Data Binding

Handling Data Binding

Angular and Vue

Both, Angular and Vue, provide a convenient way for pseudo Two-way Data Binding - [(ngModel)] and v-model. These are not actual Two-way Data Bindings but instead automatically bind the data and subscribe to the change event.

Here is an example of v-model on an input element:

<!-- Using v-model -->
<input v-model="firstName" />

<!-- ...will result in this -->
<input :value="firstName" @input="firstName = $event.target.value" />

React

React does not provide a short-hand syntax for handling Two-way data flow. Nevertheless, you can write your own hook.

Let's assume you want to use Two-way Data Binding for HTML inputs. We can create a custom hook that stores the value in a state variable and adds an event handler for the onInput event.

The hook accepts an initial value (line 1). It creates a state for the current value (line 2). The hook then returns a model (line 7) which sets the value on the input element and subscribes to the change event. Additionally, we also return the state setter to let the component change the state programmatically.

function useModel<E extends ModelElement>(initial: string = null) {
  const [value, setValue] = useState<string>(initial);
  const handler: ChangeEventHandler<E> = (e) => {
    setValue(e.currentTarget.value);
  };

  const model = { value, onChange: handler };
  return { model, setModel: setValue };
}

We can use this hook in our component (line 2). We need to spread the model on our input element (line 7). This will add both the value and the onChange event listener.

function App() {
  const { model, setModel } = useModel("John");
  const reset = () => setModel("");

  return (
    <>
      <input {...model} />
      <div>Hello {model.value}</div>
      <button onClick={reset}>Reset</button>
    </>
  );
}

The code examples above are shortened. The complete example is available on StackBlitz. It includes TypeScript definitions and a callback function for the change event:

Wrap up

We have created a simple hook for Two-way Data Binding on input elements. The hook subscribes to the change event and stores the current value in a state variable.

It's of course a very basic example with a simple API. We could improve it and add new features like handling numbers.

While it may look laborious that React does not provide this out of the box as Vue and Angular do, it allows you to create your own custom hook that better suits your needs.