Controlled Components and Uncontrolled Components

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.

Controlled 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)} />
}

Uncontrolled Components

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>
      </>
    )
  }
}

Summary

Controlled Components

  • 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 or checked prop.
  • Updating the state of a react 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's state.
    • SetState triggers the view to re-render, completing the update of the form component value.

Uncontrolled Components

  • 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 or props.
  • Typically, a ref prop needs to be added to access the rendered underlying DOM element.
  • You can specify the value by adding defaultValue.

Daily Question

https://github.com/WindrunnerMax/EveryDay

References

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