The concept of controlled and uncontrolled components in React
is in relation to forms. In React
, form elements typically hold their own internal state, so their functionality differs from other HTML
elements. The different ways to obtain the internal state of form elements give rise to controlled and uncontrolled components.
In HTML
form elements, they usually maintain their own state
and update the UI
as the user inputs. This behavior is not controlled by our program. However, by establishing a dependency between the state
property in React
and the value of form elements, and then updating the state
property through the onChange
event paired with setState()
, we can control the operations that occur in the form during user input. In React
, form input elements controlled in this way are called controlled components.
When defining an input
box in React
, it does not have the bidirectional binding feature seen in Vue
with v-model
. This means that there is no directive that can combine data with the input box and synchronize the data as the user inputs content into the input box.
class Input extends React.Component {
render () {
return <input name="username" />
}
}
When a user inputs content into the input box on the interface, it maintains its own state
. This state
is not the same as the usual this.state
we see. Instead, it is an abstract state
on each form element. This enables it to update the UI
according to user input. If we want to control the content of the input box, and the content of the input box depends on the value
property in the input
, we can define a property named username
in this.state
and specify the value
on the input as this property.
class Input extends React.Component {
constructor (props) {
super(props);
this.state = { username: "1" };
}
render () {
return <input name="username" value={this.state.username} />
}
}
However, you will notice that the input
content is read-only at this point because the value
is controlled by this.state.username
. When the user inputs new content, this.state.username
does not automatically update. As a result, the content inside the input does not change. At this point, the console usually throws a warning.
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`.
This warning actually provides the solution to this problem. We just need to listen to the input content changes with the onChange
event of the component and update this.state.username
using setState
. This way, in the current component, we can control the value of this form element, which is a controlled component.
class Input extends React.Component {
constructor (props) {
super(props);
this.state = { username: "1" };
}
render () {
return (
<>
<input name="username" value={this.state.username}
onChange={e => this.setState({username: e.target.value})}
/>
<button onClick={() => console.log(this.state.username)} >Log</button>
</>
)
}
}
It's also important to note that although this component is a controlled component, there are drawbacks if it is used as a shared component for invocation. Even though the Input
component itself is a controlled component, the calling side loses the control to change the value of the Input
component. Therefore, for the calling side, the Input
component becomes an uncontrolled component. Using an uncontrolled component's usage pattern to call a controlled component is an anti-pattern. Examples below will be written in Hooks
.
// Component Provider
function Input({ defaultValue }) {
const [value, setValue] = React.useState(defaultValue)
return <input value={value} onChange={e => setValue(e.target.value)} />
}
// Caller
function UseInput() {
return <Input defaultValue={1} />
}
If the component provider or the caller need the Input
component to be a controlled component, the provider just needs to give up control.
// Component Provider
function Input({ value, onChange }) {
return <input value={value} onChange={onChange} />
}
// Caller
function UseInput() {
const [value, setValue] = React.useState(1);
return <Input value={value} onChange={e => setValue(e.target.value)} />
}
If a form element does not go through state
but is modified through ref
or directly manipulating the DOM
, then its data cannot be controlled through state
, and this is what we call an uncontrolled component.
class Input extends React.Component {
constructor (props) {
super(props);
this.input = React.createRef();
}
render () {
return (
<>
<input name="username" ref={this.input} />
<button onClick={() => console.log(this.input.current.value)} >Log</button>
</>
)
}
}
- Whenever the form's state changes, it will be written to the component's
state
. - In a controlled component, the rendered state of the component corresponds to its
value
orchecked prop
. - Updating the
state
of areact
controlled component:- By setting the default value of the form in the initial
state
. - Whenever the form's value changes, the
onChange
event handler is called. - The event handler obtains the changed status through the synthetic object
event
and updates the application'sstate
. SetState
triggers the view to re-render, completing the update of the form component value.
- By setting the default value of the form in the initial
- If a form component does not have a
value prop
, it can be termed as an uncontrolled component. - Uncontrolled components are an anti-pattern, as their values are not controlled by the component's own
state
orprops
. - Typically, a
ref prop
needs to be added to access the rendered underlyingDOM
element. - You can specify the
value
by addingdefaultValue
.
https://github.com/WindrunnerMax/EveryDay
https://muyunyun.cn/posts/8bdf2cdf/
https://zhuanlan.zhihu.com/p/89223413
https://juejin.cn/post/6844904154133954568
https://juejin.cn/post/6858276396968951822
https://segmentfault.com/a/1190000022925043
https://segmentfault.com/a/1190000012458996
https://zh-hans.reactjs.org/docs/glossary.html