Skip to content

Two-way Data Binding in React

In React data flows one-way, from top to bottom. It does not support two-way data binding for good reasons. However, we can use Hooks to implement a pseudo two-way data binding.

Published
Updated
Reading time
3 min

Update October 2021: The post has been rewritten and the StackBlitz demo has been updated to React 17.


One-way vs. Two-way

With two-way data binding the data flows in both directions, from the parent component to the child component and vice versa. Both, the parent and the child, are allowed to mutate data.

With one-way Data Binding the data flows in one direction, from the parent component to the child component. The child component can read the data but is not allowed to update the data directly. Instead, the child component emits an event to the parent and the parent component is responsible for updating the data.

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

Older frameworks (like AngularJS, which is deprecated) support Two-way Data Binding but modern frameworks like Angular and Vue don't support this model anymore. They both work with One-way Data Binding.

Handling data binding

Angular and Vue

Angular and Vue provide a convenient for pseudo two-way data binding. This is data binding that looks like two-way data binding but is actually still one-way data binding under the hood. In Angular it is the [(ngModel)] directive, in Vue it is the v-model directive. Both of them provide the data as input (input binding), subscribe to a change event and update the data.

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. But you can write your own hook.

Implementation

Let's create our own pseudo two-way data binding hook for an HTML input element.

The hook uses a state variable to store the current value and accepts the initial value as parameter. This allows you to prefill the input element.

function useModel(initial: string = null) {
  const [value, setValue] = useState<string>(initial);
}

Next, we need to add an event handler to the input element to get notified whenever the input value changes. We will then update our state.

function useModel(initial: string = null) {
  // ...
  const handler: ChangeEventHandler<HTMLInputElement> = (e) => {
    setValue(e.currentTarget.value);
  };
}

Finally, we need to return the value and the event handler. We also return the setValue function to let the component set the value programmatically.

function useModel(initial: string = null) {
  // ...
  const model = { value, onChange: handler };
  return { model, setModel: setValue };
}

That's it. We can now use this hook in our component. 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:

stackblitz.com/edit/react-two-way-data-binding-hook

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.