Use online Editor : https://playcode.io/react
Data Binding
function App() {return (<input />);}By default, React takes a very “hands-off” approach. It creates the<input>
DOM node for us and leaves it alone. This is an uncontrolled element since React isn't actively managing it.export function App(props) {const [count, setCount] = React.useState(0);return (<div className='App'><h1>Hello React.</h1><h2>Start editing to see some magic happen!</h2><input key='123' value={count}/><button onClick={() => setCount(count + 1)}> Click Me</button></div>);Warning: You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`.%sexport function App(props) {const [count, setCount] = React.useState(0);return (<div className='App'><h1>Hello React.</h1><h2>Start editing to see some magic happen!</h2><input key='123' value={count} readOnly/><button onClick={() => setCount(count + 1)}> Click Me</button></div>);This is known as a controlled element. React is on guard, making sure that the input always displays the string 0.Instead of binding the input to a static string, we've bound the input to a state variable,count
React re-renders this component, and updates the value in the
<input>
to reflect this new reality.
We still can't type in the text input, though! React is keeping the input locked to the value of thecount
state variable.In data-binding lingo, this is known as "one-way" data binding. The input updates when the state changes, but the state doesn't update when the input is edited:export function App(props) {const [count, setCount] = React.useState(22);const [state, setState] = React.useState('Hello World');return (<div className='App'><h1>Hello React.</h1><h2>Start editing to see some magic happen!</h2><inputvalue={state}onChange={(event) => {setState(event.target.value);}}/><p><strong>Current value:</strong>{state}</p></div>);}We attach an event listener with theonChange
attribute. When the user edits the text input, this function is invoked, and theevent
object is passed in.event.target
is a reference to the DOM node that triggered the event: in this case, it's the text input. That text input has avalue
attribute, and this represents the value that the user has just tried to enter into the input.We update our React state so that it holds this new value. React re-renders, and pushes that new value into the input. The cycle is complete!This is the fundamental idea behind data binding in React. The two ingredients are:
A “controlled” field that locks the input to a piece of React state. AnonChange
handler that updates the state variable when the user edits the input.With this wired up, we have proper two-way data binding.
When working with text inputs, be sure to use an empty string (''
) as the initial state:Earlier in this tutorial, we learned that the
value
attribute allows us to create a controlled input:JS// Uncontrolled<input />// Controlled<input value="Hello World" />React decides whether an input should be controlled or uncontrolled based on whether we pass a
value
or not.Controlled inputs are meant to be an always-or-never kind of thing. React doesn't expect to suddenly be given custody of an input. If we want an input to be controlled, we need to give it a
value
100% of the time. From the very first render, to the very last.Given this information, do you see any potential issues with this code?
JSXfunction App() {const [name, setName] = React.useState();return (<inputvalue={name}onChange={(event) => {setName(event.target.value);}}/>);}Here's the problem: Because we haven't specified an initial value for the
name
state variable, it is initialized asundefined
. And so, it's essentially equivalent to doing this:JSX<inputvalue={undefined}onChange={(event) => {setName(event.target.value);}}/>
undefined
is not actually a value. When we setvalue={undefined}
, it's equivalent to omitting thevalue
attribute altogether. By-passing an undefined value, we're telling React to create an uncontrolled input.But then, when the user starts typing in the input,
value
is updated to a string. All of a sudden, we're expecting React to manage this input for us, but React isn't designed to “adopt” inputs like this.Warning: A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components%s
If we want to bind our data to an input in React, we always need to pass it a defined
value
. And so by initializing our state variable to''
, we guarantee that the input will always be given a string.Now, in practice, React will do its best to adopt an input, and it usually works; you'll get a warning, not an error. But this is a dangerous game to play, and it could lead to subtle, hard-to-diagnose bugs.
With text inputs, there's a 1:1 relationship between our state and our form control. A single piece of state is bound to a single<input>
tag.Radio buttonWith radio buttons, there are multiple inputs being bound to a single piece of state! It's a 1:many relationship. And this distinction is why things look so different.function App() {const [hasAgreed, setHasAgreed] = useState();return (<><form><fieldset><legend> Do you agree? </legend><input type="radio" name="agreed-to-terms" id="agree-yes" value="yes" checked={hasAgreed === "yes"} onChange={event => {setHasAgreed(event.target.value) }} /><label htmlFor="agree-yes"> Yes </label><br /><input type="radio" name="agreed-to-terms" id="agree-no" value="no" checked={hasAgreed === "no"} onChange={event => {setHasAgreed(event.target.value) }} /><label htmlFor="agree-no"> No </label></fieldset></form><p> <strong>Has agreed:</strong> {hasAgreed || "undefined"}</p></>);}In the example above, our state will always be equal to one of three possible values:
undefined
(no option selected)"yes"
(thevalue
of the first radio button)"no"
(thevalue
of the second radio button)Instead of tracking the value of a specific input, our state variable tracks which option is ticked.
<inputvalue="yes"onChange={(event) => {setHasAgreed(event.target.value);// Equivalent to: setHasAgreed("yes")}}/>For true two-way data-binding, we need to make this a controlled input. In React, radio buttons are controlled with the
checked
attribute:<inputvalue="yes"checked={hasAgreed === "yes"}/>By specifying a boolean value forchecked
, React will actively manage this radio button, ticking or unticking the DOM node based on thehasAgreed === "yes"
expression.Gotchas
When using iteration to dynamically create radio buttons, we need to be careful not to accidentally “re-use” a variable name used by our state variable.
Avoid doing this:
JSXconst [language, setLanguage] = React.useState();return VALID_LANGUAGES.map((language) => (<inputtype="radio"name="current-language"id={language}value={language}checked={language === language}onChange={event => {setLanguage(event.target.value);}}/>));In our
.map()
call, we're naming the map parameterlanguage
, but that name is already taken! Our state variable is also calledlanguage
.This is known as “shadowing”, and it essentially means that we've lost access to the outer
language
value. This is a problem, because we need it to accurately set thechecked
attribute!For this reason, I like to use the generic
option
name when iterating over possible options:JSXVALID_LANGUAGES.map(option => {<inputtype="radio"name="current-language"id={option}value={option}checked={option === language}onChange={event => {setLanguage(event.target.value);}}/>})
Link to this headingCheckboxes
Checkboxes are very similar to radio buttons, though they do come with their own complexities.
Our strategy will depend on whether we're talking about a single checkbox, or a group of checkboxes.
Let's start with a basic example, using only a single checkbox:
import React from 'react';function App() {const [optIn, setOptIn] = React.useState(false);return (<><form><inputtype="checkbox"id="opt-in-checkbox"checked={optIn}onChange={event => {setOptIn(event.target.checked);}}/><label htmlFor="opt-in-checkbox"><strong>Yes,</strong> I would like to join the newsletter.</label></form><p><strong>Opt in:</strong> {optIn.toString()}</p></>);}export default App;As with radio buttons, we specify that this should be a controlled input with thechecked
property. This allows us to sync whether or not the checkbox is ticked with ouroptIn
state variable. When the user toggles the checkbox, we update theoptIn
state using the familiaronChange
patternCheckbox groups
Things get a lot more dicey when we have multiple checkboxes that we want to control with React state.
Let's look at an example. See if you can work out what's happening here, by ticking different checkboxes and seeing how it affects the resulting state:
import React from 'react';const initialToppings = {anchovies: false,chicken: false,tomatoes: false,}function App() {const [pizzaToppings,setPizzaToppings] = React.useState(initialToppings);// Get a list of all toppings.// ['anchovies', 'chicken', 'tomato'];const toppingsList = Object.keys(initialToppings);return (<><form><fieldset><legend>Select toppings:</legend>{/*Iterate over those toppings, andcreate a checkbox for each one:*/}{toppingsList.map(option => (<div key={option}><inputtype="checkbox"id={option}value={option}checked={pizzaToppings[option] === true}onChange={event => {setPizzaToppings({...pizzaToppings,[option]: event.target.checked,})}}/><label htmlFor={option}>{option}</label></div>))}</fieldset></form><p><strong>Stored state:</strong></p><p className="output">{JSON.stringify(pizzaToppings, null, 2)}</p></>);}export default App;Unlike with radio buttons, multiple checkboxes can be ticked. This changes things when it comes to our state variable.With radio buttons, we can fit everything we need to know into a single string: the
value
of the selected option. But with checkboxes, we need to store more data, since the user can select multiple options.There are lots of ways we could do this. My favourite approach is to use an object that holds a boolean value for each option:
const initialToppings = {anchovies: false,chicken: false,tomatoes: false,}In the JSX, we map over the keys from this object, and render a checkbox for each one. In the iteration, we look up whether this particular option is selected, and use it to control the checkbox with the
checked
attribute.We also pass a function to
onChange
that will flip the value of the checkbox in question. Because React state needs to be immutable, we solve this by creating a near-identical new object, with the option in question flipped between true/false.(We can also specify a
name
, as with radio buttons, though this isn't strictly necessary when working with controlled inputs.)
Select
Like radio buttons, the
<select>
tag lets the user select one option from a group of possible values. We generally use<select>
in situations where there are too many options to display comfortably using radio buttons.import React from 'react';function App() {const [age, setAge] = React.useState('0-18');return (<><form><label htmlFor="age-select">How old are you?</label><selectid="age-select"value={age}onChange={event => {setAge(event.target.value)}}><option value="0-18">18 and under</option><option value="19-39">19 to 39</option><option value="40-64">40 to 64</option><option value="65-infinity">65 and over</option></select></form><p><strong>Selected value:</strong>{age}</p></>);}export default App;
In React, <select> tags are very similar to text inputs. We use the same
value
+onChange
combo. Even theonChange
callback is identical!Gotchas
As with text inputs, we need to initialize the state to a valid value. This means that our state variable's initial value must match one of the options:
// This initial value:const [age, setAge] = React.useState("0-18");// Must match one of the options:<select><optionvalue="0-18">18 and under</option></select>
This is a smelly fish. One small typo, and we risk running into some very confusing bugs.
To avoid this potential footgun, better to generate the
<option>
tags dynamically, using a single source of truth:import React from 'react';// The source of truth!const OPTIONS = [{label: '18 and under',value: '0-18'},{label: '19 to 39',value: '19-39'},{label: '40 to 64',value: '40-64'},{label: '65 and over',value: '65-infinity'},];function App() {// Grab the first option from the array.// Set its value into state:const [age, setAge] = React.useState(OPTIONS[0].value);return (<><form><label htmlFor="age-select">How old are you?</label><selectid="age-select"value={age}onChange={event => {setAge(event.target.value)}}>{/*Iterate over that array, to createthe <option> tags dynamically:*/}{OPTIONS.map(option => (<optionkey={option.value}value={option.value}>{option.label}</option>))}</select></form><p><strong>Selected value:</strong>{age}</p></>);}export default App;
https://www.joshwcomeau.com/react/data-binding/