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