In this article, I will introduce the React lifecycle scenarios in real projects and give code examples.

Map the React life cycle: the projects. Wojtekmaj. Pl/React – lifec…

Mounting Stage (Mounting)

The component instance is created and inserted into the DOM. This phase calls life cycle functions in the order coustructor(), static getDerivedStateFromProps(), Render (), componentDidMount().

Updating phase

Updates are triggered when a component’s state or props changes. This phase calls the lifecycle functions in the following order: Static getDerivedStateFromProps(), shouldComponentUpdate(), Render (), getSnapshotBeforeUpdate(), componentDidUpdate()

UnMounting Phase (UnMounting)

When a component is removed from the DOM, the componentWillUnmount() method is called.

Error boundaries

Methods like static getDerivedStateFromError(), componentDidCatch() are called when an error is thrown in a render process, lifecycle, or child component constructor.

render()

  1. The Render () function is the only method that must be implemented in a class component;
  2. The return value of the render function: React element, array, Fragments, Portals, string or value, Boolean or NULL;
  3. Render should be a pure function and not interact with the browser to make the component easier to understand.

constructor(props)

Call time:

The React component calls its constructor before it is mounted.

Usage Scenarios:
  1. Initialize the state
  2. Bind instances to event handlers
class Test extends React.Component {
   constructor(props) {
      super(props);
      this.state = { counter: 0 };
      this.handleClick = this.handleClick.bind(this);
   }

   handleClick() {
      const { counter } = this.state;
      this.setState({ counter: counter + 1 });
   }

   render() {
      return (
         <div>
             {this.state.counter}
             <button onClick={this.handleClick}>click</button> 
         </div>
      );
   }
}
ReactDOM.render(
   <Test />.document.getElementById('root'));Copy the code
Note ⚠ ️ :
  1. Do not call the setState method in the constructor;
  2. When implementing the constructor for the react.componentsubclass, you should call super(props) before any other statements;
  3. Avoid introducing side effects or subscriptions in constructors;
  4. Avoid assigning the value of props to state.

componentDidMount()

Call time:

Called immediately after the React component is mounted (inserted into the DOM tree).

Usage Scenarios:
  1. Initialization operations that depend on DOM nodes;
  2. Obtain data through network request;
  3. Add a subscription;
Note ⚠ ️ :

If setState() is called here, it will trigger rendering, which will occur before the browser updates the screen, so the user will not be aware of it, but use this mode with caution as it can cause performance issues. This can be used if your rendering depends on the location and size of DOM nodes, such as Modal and Tooltip.

componentDidUpdate(prevProps, prevState, snapshot)

Call time:

This method is called immediately after the React component is updated, not the first rendering.

Usage Scenarios:
  1. This is where you manipulate the DOM after the component is updated;
  2. Compare the props before and after the update and perform the operations (such as network requests, setState(), etc.) if there are any changes.
componentDidUpdate(prevProps) {
  // Typical usage (don't forget to compare props) :
  if (this.props.id ! == prevProps.id) {this.fetchData(this.props.id); }}Copy the code
Note ⚠ ️ :
  1. Do not “mirror” props to state;
  2. If the component implements the getSnapshotBeforeUpdate() life cycle, its return value is passed as the third parameter (snapshot) to componentDidUpdate; otherwise, the third parameter is undefined;
  3. If shouldComponentUpdate() returns false, componentDidUpdate() is not called.

componentWillUnmount()

Call time:

Called before the React component is uninstalled or destroyed

Usage Scenarios:
  1. Clear timer (setInterval());
  2. Cancel the network request;
  3. Unsubscribe subscriptions created in componentDidMount().
Note ⚠ ️ :

Do not call setState() during this life cycle, because after the component is unloaded, it is not re-rendered.

shouldComponentUpdate(nextProps, nextState)

Call time:

ShouldComponentUpdate () is called before render() when state or props is changed, but not for the first render or forceUpdate().

Usage Scenarios:

ShouldComponentUpdate () can be overridden by writing the React component to optimize its performance, but in most cases we can replace shouldComponentUpdate() with a shouldComponentUpdate() inheritance. PureComponent implements the shouldComponentUpdate() method (a shallow comparison of the current and previous props and state).

class CounterButton extends React.Component {
  constructor(props) {
    super(props);
    this.state = {count: 1};
  }

  shouldComponentUpdate(nextProps, nextState) {
    if (this.props.color ! == nextProps.color) {return true;
    }
    if (this.state.count ! == nextState.count) {return true;
    }
    return false;
  }

  render() {
    return (
      <button
        color={this.props.color}
        onClick={()= > this.setState(state => ({count: state.count + 1}))}> Count: {this.state.count} </button>); }}Copy the code
Note ⚠ ️ :
  1. This method returns true by default, which follows the default behavior — “Re-render component when state or props changes”; If false is returned, the component will not be re-rendered, and in most cases you should follow the default behavior.
  2. This method exists for performance optimization, do not try to use this method to block rendering, as this can be buggy;
  3. If you must write this method manually, you need to compare this.props to nextProps and this.state to nextState.
  4. It is not recommended to use json.stringfy () in shouldComponentUpdate() or to make deep comparisons, as this is extremely performance costly.

static getDerivedStateFromProps(props,state)

Call time:

Called before the Render () method, both during the initial mount and during subsequent updates.

Usage Scenarios:

This applies to rare use cases where the value of state depends on props at all times.

// Example 1:
class Example extends React.Component {
  state = { 
    isScrollingDown: false.lastRow: null
  };

  static getDerivedStateFromProps(props, state) { 
    if(props.currentRow ! == state.lastRow) {return { 
        isScrollingDown: props.currentRow > state.lastRow, 
        lastRow: props.currentRow
      }; 
    }
    // Returning null indicates that state does not need to be updated.
    return null; }}Copy the code
Example 2: Get externalData from props
class ExampleComponent extends React.Component {
  state = {
    externalData: null};static getDerivedStateFromProps(props, state) {
  // Save the prevId in state so that we can compare the props as they change.
  // Clear previously loaded data (so we don't render old content).
    if(props.id ! == state.prevId) {return { 
        externalData: null.prevId: props.id
      };
    } 
    // There is no need to update state
    return null;
  }
   
  componentDidMount() {
    this._loadAsyncData(this.props.id);
  }

  componentDidUpdate(prevProps, prevState) { 
    if (this.state.externalData === null) { 
      this._loadAsyncData(this.props.id); }}componentWillUnmount() {
    if (this._asyncRequest) {
      this._asyncRequest.cancel(); }}render() {
    if (this.state.externalData === null) {
      // Render load state...
    } else {
      // Render real UI...}}_loadAsyncData(id) {
    this._asyncRequest = loadMyAsyncData(id).then(
      externalData= > {
        this._asyncRequest = null;
        this.setState({externalData}); }); }}Copy the code
Note ⚠ ️ :

Derived state leads to code redundancy and makes components difficult to maintain. Alternatives are as follows:

(1) Memoization Mode: There is no need to use getDerivedStateFromProps() if you want to cache the results of an evaluation based on the current props, because the complexity of managing state increases as more properties need to be managed. For example, If we want to add a second derived state to the component state, we need to write two copies of the logic that tracks the changes. To keep your code simple and manageable, try memoization.

  / / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  // Note: this example is not the recommended method.
  // The following example is the suggested method.
  / / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

  static getDerivedStateFromProps(props, state) {
    // Refilter when list changes or filter text changes.
    // Notice that we store prevFilterText and prevPropsList to detect changes.
    if( props.list ! == state.prevPropsList || state.prevFilterText ! == state.filterText ) {return {
        prevPropsList: props.list,
        prevFilterText: state.filterText,
        filteredList: props.list.filter(item= > item.text.includes(state.filterText))
      };
    }
    return null;
  }
Copy the code
/ / use memoization
import memoize from "memoize-one";

class Example extends Component {
  // state just saves the current filter value:
  state = { filterText: "" };

  // Re-run filter when list or filter changes:
  filter = memoize(
    (list, filterText) = > list.filter(item= > item.text.includes(filterText))
  );

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

  render() {
    // Computes the latest filtered list.
    // If it is the same as the last render argument, 'memoize-one' will repeat the last value.
    const filteredList = this.filter(this.props.list, this.state.filterText);

    return (
      <Fragment>
        <input onChange={this.handleChange} value={this.state.filterText} /> 
        <ul>{filteredList.map(item => <li key={item.id}>{item.text}</li>)}</ul>
      </Fragment>); }}Copy the code

(2) Use fully controlled components:

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

(3) Using a non-controllable component with a key (React creates a new component when the key changes instead of an existing component). Most of the time, this is the best way to handle resetting state.

class EmailInput extends Component {
  state = { email: this.props.defaultEmail };

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

  render() {
    return <input onChange={this.handleChange} value={this.state.email} />;
  }
}

<EmailInput
  defaultEmail={this.props.user.email}
  key={this.props.user.id}
/>
Copy the code

In some cases, however, the key may not work, so you can use getDerivedStateFromProps() to observe property changes.

class EmailInput extends Component {
  state = {
    email: this.props.defaultEmail,
    prevPropsUserID: this.props.userID
  };

  static getDerivedStateFromProps(props, state) {
    // As long as the current user changes,
    // Reset all user-related states.
    // In this example, only email and user are related.
    if(props.userID ! == state.prevPropsUserID) {return {
        prevPropsUserID: props.userID,
        email: props.defaultEmail
      };
    }
    return null;
  }

  // ...
}
Copy the code

getSnapshotBeforeUpdate(prevProps, prevState)

Call time:

Called before the last render output (committed to the DOM node).

Usage Scenarios:

Components can retrieve information from the DOM before making changes, which can be used in UI processing, such as scrolling positions.

class ScrollingList extends React.Component {
  constructor(props) {
    super(props);
    this.listRef = React.createRef();
  }

  getSnapshotBeforeUpdate(prevProps, prevState) {
    // Do we add new items to the list?
    // Capture the scroll position so we can adjust the scroll position later.
    if (prevProps.list.length < this.props.list.length) {
      const list = this.listRef.current;
      return list.scrollHeight - list.scrollTop;
    }
    return null;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    // If our snapshot has a value, we just added a new item,
    // Adjust the scroll position so that these new items do not push the old items out of the view.
    // (where snapshot is the return value of getSnapshotBeforeUpdate)
    if(snapshot ! = =null) {
      const list = this.listRef.current; list.scrollTop = list.scrollHeight - snapshot; }}render() {
    return (
      <div ref={this.listRef}>{/ *... contents... * /}</div>); }}Copy the code

static getDerivedStateFromError(error)

Call time:

This method is called when a component throws an error. It takes the thrown error as an argument and returns a value to update state.

Usage Scenarios:

Display degraded UI

Note ⚠ ️ :

This method is called at render time, so no side effects are allowed, so use componentDidCatch().

componentDidCatch(error, info)

Call time:

This method is called in the commit phase. Therefore, side effects are allowed to be performed.

Usage Scenarios:

For logging errors, the second argument to this method contains information about the stack that caused the component error.

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { error: null.errorInfo: null };
  }
  
  static getDerivedStateFromError(error) {
    // Update state so that the next rendering can display the degraded UI
    return { error: true };
  }
  
  componentDidCatch(error, errorInfo) {
    this.setState({ errorInfo })
  }
  
  render() {
    if (this.state.errorInfo) {
      return (
        <div>
          <h2>Something went wrong.</h2>
          <details style={{ whiteSpace: 'pre-wrap' }}>
             {this.state.error && this.state.error.toString()}
             <br />
             {this.state.errorInfo.componentStack}
          </details>
        </div> 
      );
    }
    return this.props.children; }}class BuggyCounter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { counter: 0 };
    this.handleClick = this.handleClick.bind(this);
  }
    
  handleClick() {
    this.setState(({counter}) = > ({ counter: counter + 1 }));
  }
  
  render() {
    if (this.state.counter === 5) {
      // Render here produces an error
      return [1.2.3];
    }
    return <h1 onClick={this.handleClick}>{this.state.counter}</h1>; }}function App() {
  return (
    <div>
       <p>
           <b> This is an example of error boundaries in React 16.
               <br /><br />
               Click on the numbers to increase the counters.
               <br />
               The counter is programmed to throw when it reaches 5. This simulates a JavaScript error in a component.
           </b>
       </p>
       <hr />
       <ErrorBoundary>
           <p>These two counters are inside the same error boundary. If one crashes, the error boundary will replace both of them.
           </p>
       <BuggyCounter /> 
           <BuggyCounter />
       </ErrorBoundary>
       <hr />
       <p>These two counters are each inside of their own error boundary. So if one crashes, the other is not affected.
       </p>
       <ErrorBoundary>
           <BuggyCounter />
       </ErrorBoundary> 
       <ErrorBoundary>
           <BuggyCounter />
       </ErrorBoundary> 
    </div>
  );
}
Copy the code