preface

Although I have been developing react for some time, I do not have a deep understanding of the controlled/uncontrolled components. Most of the development process is controlled components, and I understand the controlled components: React: A react component is a controlled component because all properties it needs come from the parent component and the methods used to change properties come from the parent component. React: a react component is a controlled component

A case in point

Before we start, let’s take a look at an example. This example is a code written by another colleague in my work. I found this code when I did a code review for him. There is a button on the page, click the button to change the popover, then click the cancel button in the popover or click the OK button to close the popover, and click the button again to open the popover, which is a simple modal box logic, the code is as follows:

demo.js:

import React, { Component, Fragment } from 'react'; import { Button } from 'antd'; import MyModal from './modal'; class Demo extends Component { state = { modalVisible: false } handleModalVisible = () => { this.setState({ modalVisible: true }); } render() { const { modalVisible } = this.state; Return (<Fragment> <Button type="primary" onClick={this.handleModalVisible} > Open popup </Button> <MyModal modalVisible={modalVisible} /> </Fragment> ); } } export default Demo;Copy the code

modal.js:

import React, { Component } from 'react'; import { Modal } from 'antd'; class MyModal extends Component { state = { visible: this.props.modalVisible } componentWillReceiveProps(nextProps) { this.setState({ visible: nextProps.modalVisible }); } handleCloseModal = () => { this.setState({ visible: false }); } render() { const { visible } = this.state; Return (<Modal title=" Modal box "visible={visible} onOk={this.handleclosemodal} onCancel={this.Handleclosemodal} > <div> I'm a Modal box </div> </Modal>); } } export default MyModal;Copy the code

The popover here used ANTD modal, and the above code could complete the requirement, but at this time our browser console reported a warning:

componentWillReceiveProps has been renamed, and is not recommended for use. See https://reactjs.org/link/unsafe-component-lifecycles for details.

ComponentWillReceiveProps has been renamed the life cycle method, and it is not recommended to use, for details, please refer to: https://reactjs.org/link/unsafe-component-lifecycles

Check and found its new name is: UNSAFE_componentWillReceiveProps

And why I don’t recommend doing it this way, because this requirement works perfectly as a controlled component: Popup window that decided it needed to render or attributes must be from outside, so in this case, pop-up internal don’t need to maintain the property, after all is from outside, it’s meaningless to their maintenance, it is obvious that modify the properties of the method would be like this property from outside, It would be a good solution to change it to the following form:

demo.js:

import React, { Component, Fragment } from 'react'; import { Button } from 'antd'; import MyModal from './modal'; class Demo extends Component { state = { modalVisible: false } handleModalVisible = () => { this.setState({ modalVisible: true }); } handleCloseModal = () => { this.setState({ modalVisible: false }); } render() { const { modalVisible } = this.state; Return (<Fragment> <Button type="primary" onClick={this.handleModalVisible} > Open popup </Button> <MyModal modalVisible={modalVisible} closeModal={this.handleCloseModal} /> </Fragment> ); } } export default Demo;Copy the code

modal.js:

import React, { Component } from 'react'; import { Modal } from 'antd'; class MyModal extends Component { render() { const { modalVisible, closeModal } = this.props; Return (<Modal title=" Modal box "visible={modalVisible} onOk={closeModal} onCancel={closeModal} > <div> I am a Modal box </div> </Modal> ); } } export default MyModal;Copy the code

So it’s pretty clear: the external property renders, makes the popover open, and then after processing the logic calls an external method to close the popover, and that closing method also comes from outside because you need to manipulate the property that’s also outside that makes the popover open

Actually code changes with respect to ok, actually also is such, of course, the project has also launched at the same time, but if ends there also won’t have this article, the cause is when I was in the react website search componentWillReceiveProps it inside a description:

If you 2 componentWillReceiveProps to “reset” some state when a prop changes, consider either making a component fully controlled or fully uncontrolled with a key instead.

If you use componentWillReceiveProps to reset when a prop to change some of the state, so please consider using completely controlled components or not controlled with a key component to replace

This description said it was the beginning of the code, the code used in componentWillReceiveProps purpose is reset when a prop to change some of the state, it also aroused my curiosity, therefore also had the article

ComponentWillReceiveProps execution time

So why componentWillReceiveProps will have a new name: UNSAFE_componentWillReceiveProps? And why is it unsafe to start with? To answer those two questions, we need to learn about the timing of its execution, which comes from the official Unconditionally guide: Anti-Pattern: Copying Props to State, and it’s worth rereading for a moment:

A common misconception is that getDerivedStateFromProps and componentWillReceiveProps are only called when props These lifecycles are called any time a parent component rerenders, Regardless of whether the props are “different” from before. Because of this, it has always been unsafe to unconditionally override state using either of these lifecycles. Doing so will cause state updates to be lost.

A common misconception is: GetDerivedStateFromProps and componentWillReceiveProps only when props change implementation, and is, in fact, as long as the parent component to apply colours to a drawing, then they will be executed, whether than before to render props and “different”, Therefore, it is not safe to use any of them to override state unconditionally, and doing so will result in state updates being lost

ComponentWillReceiveProps, in short, the execution time is: parent component rendering

What problems might be caused by using it

Knowing its execution timing, we can discuss why it is not recommended to use it, that is, what problems it may cause. First, let’s take a look at an example, from which we can see the problem of lost status updates clearly and intuitively: reactjs-org-when-to-use-derived-state-example-1

As you can see from reading the code, our EmailInput component initializes a state value through a specific props from the parent component Timer, and when we update this value, that is, when we enter a value in the input box, the value we input will also update normally. In this example, we enter a value that is updated, but the value is overwritten immediately after the update. That is, the new value we entered exists for a short time, and then is overwritten immediately by the initial value, which means that our update is lost!

Why is this? A closer look at the code shows that the parent component is updated!

When we update the value of the child component EmailInput, the Timer is also updated, so that the newly entered value is overwritten, that is, the update is lost. Of course, it is not because we update the value of the child component EmailInput that the parent component Timer is updated. Instead, Timer has a Timer in componentDidMount that sets state every second, causing the Timer to update every second, setting the initial value for EmailInput as it updates: < EmailInput email = “[email protected]” / >, and parent component to apply colours to a drawing, we know the componentWillReceiveProps EmailInput also will be executed, The action is to initialize the props email passed by the parent component into our state, causing any value we enter to be overwritten by the email=”[email protected]”, causing our update to be lost. We also notice that the parent component is re-rendered, But the prop email to the sub-component remains unchanged, always [email protected], and the key code is as follows:

Timer:

/ /... componentDidMount() { this.interval = setInterval( () => this.setState(prevState => ({ count: prevState.count + 1 })), 1000 ); } / /...Copy the code

EmailInput:

state = { email: this.props.email }; / /... componentWillReceiveProps(nextProps) { this.setState({ email: nextProps.email }); } / /...Copy the code

This example vividly illustrates the problem of missing updates mentioned above, even though we used nextProps. Email before resetting state! == this.state.email == this.state. That brings us to the subject of this article: controlled and uncontrolled components

The controlled components

First, let’s take a look at Controlled Components. See Controlled Components documentation for details.

In HTML, form elements such as <input>, <textarea>, and <select> typically maintain their own state and update it based on user input. In React, mutable state is typically kept in the state property of components, and only updated with setState().

In HTML, form elements such as ,

We can combine the two by making the React state be the “single source of truth”. Then the React component that renders a form also controls what happens in that form on subsequent user input. An input form element whose value is controlled By React in this way is called a controlled component.

We can combine the two by making the React state a single source of truth. The React component that renders the form also controls what happens to the form during subsequent user input. React controls the value of an input form element in this way. This is called a controlled component.

In short: when a parent component controls the data of a child component, that child component is called a controlled component. Reactjs-org-when-use-derived -state-alternative-pattern-1, where the parent component tells the child component by email what value to display, is the one I started with. That’s what I modified in the popover example above

For example, in the example of the controlled component, the value of the child component comes from the parent component. The parent component gives an initial value to the child component and a method to modify the value passed by the parent component. When the child component changes the value, the method is called to modify the value. The parent component changes the value through setState, and passes the new value to the child component after re-rendering, so that the child component’s UI is updated

Writing this way makes the data flow visible, one-way, always top to bottom, easy to manage, and most importantly it doesn’t cause updates to be lost

Uncontrolled component

There is another way to do this, which is to use uncontrolled components, but let’s first look at the official documentation: Uncontrolled Components, when the form data is handled by the DOM element itself, rather than controlled by the parent component, it is an Uncontrolled component. It also has a feature that there is no event to update the data. Instead, a new component instance is created using the key to replace the original component. Instead of updating existing components

Again, learn about our uncontrolled component with an example: reactjs-org-when-to-use-derived-state-alternative-pattern-1

In the example we can see:

AccountsList.js:

/ /... const { accounts } = this.props; const { selectedIndex } = this.state; const selectedAccount = accounts[selectedIndex]; <UncontrolledEmailInput key={selectedAccount.id} defaultEmail={selectedAccount.email} /> //...Copy the code

UncontrolledEmailInput.js:

import React, { Component } from "react"; export default class UncontrolledEmailInput extends Component { state = { email: this.props.defaultEmail }; handleChange = event => { this.setState({ email: event.target.value }); }; render() { return ( <label> Email: <input onChange={this.handleChange} value={this.state.email} /> </label> ); }}Copy the code

AccountsList also passes a key attribute to UncontrolledEmailInput. Note that we can’t use the key attribute directly. Key will always be undefined. So you should pass in a new props, and then use that new props

UncontrolledEmailInput internally maintains its own data, value from its own state, and onChange changes its own state. Of course, the initial value is from the parent component AccountsList

All right, so that’s it for controlled and uncontrolled components. If you have any questions, please feel free to discuss them in the comments section. Finally, if you think this article is well written, don’t forget to give me a thumbs up

References:

  1. Anti-pattern: Unconditionally copying props to state
  2. reactjs-org-when-to-use-derived-state-example-1
  3. Controlled Components
  4. reactjs-org-when-to-use-derived-state-alternative-pattern-1
  5. Uncontrolled Components
  6. Keys
  7. reactjs-org-when-to-use-derived-state-alternative-pattern-1