The article contains the following content

  • Controlled and uncontrolled components
    • Uncontrolled component
    • The controlled components
  • Controlled and uncontrolled component boundaries
  • anti-patterns
  • The solution

preface

In HTML, form elements (/

<form>
  <label>Name:<input type="text" name="name" />
  </label>
  <input type="submit" value="Submit" />
</form>
Copy the code

In this HTML, we can enter any value we want in the input. What should we do if we need to retrieve the input from the current input?

Controlled and uncontrolled components

Uncontrolled Components (uncontrolled Component)

With uncontrolled components, instead of writing a data-handling function for each status update, you hand the form data to a DOM node for processing, which can be retrieved using a Ref

In an uncontrolled component, you want to be able to assign an initial value to the form, but not control subsequent updates. You can specify a defaultValue using defaultValue

class Form extends Component {
  handleSubmitClick = () = > {
    const name = this._name.value;
    // do something with `name`
  }
  render() {
    return (
      <div>
        <input
          type="text"
          defaultValue="Bob"
          ref={input= > this._name = input}
        />
        <button onClick={this.handleSubmitClick}>Sign up</button>
      </div>); }}Copy the code

Controlled Component

In React, mutable state is usually stored in the component’s state property and can only be updated by setState

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: 'shuangxu'};
  }
  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>Name:<input type="text" value={this.state.value}/>
        </label>
        <input type="submit" value="Submit" />
      </form>); }}Copy the code

In the code above, the value property value is set on the input, so the value is always displayed as this.state.value, making state the only data source.

const handleChange = (event) = > {
  this.setState({ value: event.target.value })
}

<input type="text" value={this.state.value} onChange={this.handleChange}/>
Copy the code

If we had written the handleChange method in the above example, each keystroke would execute the method and update the React state, so the form’s value would change with user input

The React component controls what happens to the form during user input and state is the only data source. Form input elements that React controls values in this way are called controlled components

📌 For controlled components, the input value is always driven by the React state!!

Controlled and uncontrolled component boundaries

Uncontrolled component

The Input component only accepts a default defaultValue. When calling the Input component, you just need to pass the defaultValue through props

/ / component
function Input({defaultValue}){
  return <input defaultValue={defaultValue} />  
}

/ / call
function Demo(){
  return <Input defaultValue='shuangxu' />
}
Copy the code

The controlled components

Displaying and changing values requires state and setState, the component controls state internally, and implements its own onChange method

/ / component
function Input() {
  const [value, setValue] = useState('shuangxu')
  return <input value={value} onChange={e= >setValue(e.target.value)} />;
}

/ / call
function Demo() {
  return <Input />;
}
Copy the code

Is the Input component controlled or uncontrolled? If we change this component and its invocation the way we wrote it before

/ / component
function Input({defaultValue}) {
  const [value, setValue] = useState(defaultValue)
  return <input value={value} onChange={e= >setValue(e.target.value)} />;
}

/ / call
function Demo() {
  return <Input defaultValue='shuangxu' />;
}
Copy the code

The Input component at this point is itself a controlled component, driven by unique state data. However, for Demo, we do not have a data change right for the Input component, so for Demo component, the Input component is an uncontrolled component. (‼ ️ It is an anti-pattern to call a controlled component as if it were an uncontrolled component.)

How can I modify the current Input and Demo component code so that the Input component itself is a controlled component and also a controlled component for the Demo component?

function Input({value, onChange}){
  return <input value={value} onChange={onChange} />
}

function Demo(){
  const [value, setValue] = useState('shuangxu')
  return <Input value={value} onChange={e= > setValue(e.target.value)} />
}
Copy the code

📌 The division of boundaries between controlled and uncontrolled components depends on whether the current component has control over changes to child component values. If the subcomponent that has control rights is controlled for the current component; The opposite is uncontrolled.

Anti-pattern – To invoke a controlled component as if it were an uncontrolled component

Although controlled and uncontrolled are commonly used to refer to inputs for a form, they can also be used to describe components whose data changes frequently.

Through the boundary division of controlled and uncontrolled components in the previous section, we can simply classify them as:

  • If the data is passed in using props, the component is considered controllable to the parent, and there are corresponding methods for processing the data
  • The data is only stored in state within the component, which is not controlled by the parent

⁉ ️ What is derived state

Simply put, if some data in a component’s state comes from outside, that data is called derived state. Most of the problems with derived state are caused by two reasons:

  • Copy props to state directly
  • Update state if the props and state are inconsistent

Copy prop directly to State

GetDerivedStateFromProps and componentWillReceiveProps execution time When the parent to render, regardless of whether the props have change, the two life cycle will be performed

Therefore, it is not safe to copy props to state directly in both methods, causing state to render incorrectly

class EmailInput extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      email: this.props.email   // The initial value is email in props
    };
  }
  componentWillReceiveProps(nextProps) {
    this.setState({ email: nextProps.email });   // Reassign state when updating
  }
  handleChange = (e) = > {
    this.setState({ email: e.target.value });
  };
  render() {
    const { email } = this.state;
    return <input value={email} onChange={this.handleChange} />; }}Copy the code

Click to see examples

Set the initial value passed to the input props, which modifies state on input. But if the parent component rerenders, the value of the input box is lost and becomes the default value for props

Even if we compare nextProps. Email before resetting! ==this.state.email will still cause updates

For this small demo, shouldComponentUpdate can be used to compare the email in props to see if it is modified and then decide if it needs to be rerendered. However, this approach is not feasible in practical applications, where a component receives multiple prop, and any change to prop will result in re-rendering and incorrect state reset. Add inline functions and object prop, and creating a fully reliable shouldComponentUpdate becomes increasingly difficult. ShouldComponentUpdate this lifecycle is more for performance optimization than handling derived states.

By this point, clarify why prop cannot be copied directly to State. Consider another question. What if only the email property in props was used to update the component?

Modify state after the props changes

Following the example above, update the component only with props. Email to prevent bugs caused by changing state

class EmailInput extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      email: this.props.email   // The initial value is email in props
    };
  }
  componentWillReceiveProps(nextProps) {
    if(nextProps.email ! = =this.props.email){
      this.setState({ email: nextProps.email });   // Assign state again when email changes}}/ /...
}
Copy the code

With this modification, the component will only re-assign state if props. Email changes.

In the following scenario, when switching between two accounts with the same email address, the input box does not reset because the prop value passed by the parent component does not change

Click to see examples

This scenario is constructed, perhaps oddly designed, but errors like this are common. For this antipattern, there are two solutions to these problems. The key is to ensure that there is only one source for any data, and to avoid copying it directly.

The solution

Fully controllable components

Remove state from the EmailInput component and use props directly to get the value, giving control of the controlled component to the parent component.

function EmailInput(props){
  return <input onChange={props.onChange} value={props.email}/>
}
Copy the code

If you want to save temporary values, you need the parent component to perform the save manually.

An uncontrolled component with a key

Let the component store temporary email states. The initial value of email is still received through prop, but the changed value is independent of prop

function EmailInput(props){
  const [email, setEmail] = useState(props.email)
  return <input value={email} onChange={(e)= > setEmail(e.target.value)}/>
}
Copy the code

In the previous example of switching accounts, you could use the React special property key to switch between different values on different pages. React creates a new component when the key changes rather than simply updating the existing component (get more). We often use key values when rendering dynamic lists, and we can use them here as well.

<EmailInput
  email={account.email}
  key={account.id}
/>
Copy the code

Click to see examples

Each time the ID changes, EmailInput is recreated and its state is reset to the most recent email value.

alternative

  1. Using the key property to do so resets the state of the entire component. Can be in getDerivedStateFromProps and componentWillReceiveProps to observe the change of the id, but possible trouble

    class EmailInput extends Component {
      state = {
        email: this.props.email,
        prevId: this.props.id
      };
    
      componentWillReceiveProps(nextProps) {
        const { prevId } = this.state;
        if(nextProps.id ! == prevId) {this.setState({
            email: nextProps.email,
            prevId: nextProps.id }); }}// ...
    }
    Copy the code

    Click to see examples

  2. Use instance methods to reset uncontrolled components

    In both cases, there is a unique identifier. If you do not have the appropriate key value, you also want to recreate the component. The first option is to generate random or incrementing values as key values, and the other option is to use sample methods to force a reset of the internal state

    class EmailInput extends Component {
      state = {
        email: this.props.email
      };
    
      resetEmailForNewUser(newEmail) {
        this.setState({ email: newEmail });
      }
    
      // ...
    }
    Copy the code

    The parent component calls this method using ref. Click to see an example

So what do we choose?

In our business development, try to choose the controlled components, use less derived the state, the use of excessive componentWillReceiveProps may cause the imperfection of the props judgment, is repeated rendering dead circulation problems.

In component library development, such as ANTD, both controlled and uncontrolled invocation methods are open to the user and the user can choose the corresponding invocation method. For the Form component, we often use getFieldDecorator and initialValue to define Form items, but we don’t care about the input process. We get all the Form values with getFieldsValue or validateFields at the end of the submission. This is an uncontrolled invocation. Or, if we have only one Input, we can directly bind the value and onChange events, which are called in a controlled manner.

conclusion

In this article, we first introduced the concepts of uncontrolled components and controlled components. For a controlled component, the component controls the process of user input and state is the only source of data for the controlled component.

We then cover the issue of component invocation, whether the component provider is a controlled component for the component caller. For callers, component controlled and uncontrolled boundary demarcation depends on whether the current component has control over changes to child component values.

The anti-pattern usage of invoking a managed component as an uncontrolled component is followed by examples. Instead of copying props directly to state, use controlled components. For uncontrolled components, if you want to reset state when a prop changes, you have the following options:

  • Suggestion: UsekeyProperty to reset all internal initial states
  • Option 1: Change only certain fields and observe changes to specific properties (properties that are unique)
  • Option 2: UserefCalling instance methods

It concludes with a summary of how to choose controlled or uncontrolled components.

Refer to the link

  • React — Controlled Components
  • React — Uncontrolled Components
  • controlled vs. uncontrolled form inputs
  • Transitioning from uncontrolled inputs to controlled
  • Redefine controlled uncontrolled components
  • You may not need to use derived state