Elimination of Component Side Effects Based on React Effect Hook Analysis

A component that subscribes to friends online

We subscribed to our friends'online status through ID while we were at DidMount.
And to prevent memory leaks, we need to clear subscriptions at WillUnmount

But when components are displayed on the screen, what happens when friend prop changes? Our component will continue to show the original friend status. This is a bug. And we also suffer from memory leaks or crashes because we use the wrong friend ID when canceling subscriptions.

class FriendStatus extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isOnline: null };
    this.handleStatusChange = this.handleStatusChange.bind(this);
  }

  componentDidMount() {
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  handleStatusChange(status) {
    this.setState({
      isOnline: status.isOnline
    });
  }

  render() {
    if (this.state.isOnline === null) {
      return 'Loading...';
    }
    return this.state.isOnline ? 'Online' : 'Offline';
  }
}

Optimizing components for subscribing to friends online

In order to solve the BUG caused by props update, we need to modify the subscription in DidUpdate


  componentDidMount() {
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  componentDidUpdate(prevProps) {
    // friend.id before unsubscribe
    ChatAPI.unsubscribeFromFriendStatus(
      prevProps.friend.id,
      this.handleStatusChange
    );
    // Subscribe to a new friend.id
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

Introducing Hooks code can be very simple

Effect Hook

useEffect() lets you perform side effects in function components
By default, it executes after the first rendering and after each update.

If you are familiar with the life cycle function of React class, you can think of useEffect Hook as a combination of three functions: component DidMount, component DidUpdate and component WillUnmount.

This is the most basic Hooks application on React's official website.

import React, { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  // Similar to componentDidMount and componentDidUpdate:
  useEffect(() => {
    // Update the document title using the browser API
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

effect to be cleared

Why do we need to return a function in effect? This is an optional clearance mechanism for effect. Each effect can return a cleanup function. This brings together the logic of adding and removing subscriptions. They are all part of the effect.

When does React clear the effect? React performs cleanup operations when components are uninstalled. As you've learned before, effect is executed every time it's rendered. That's why React will clean up the previous effect before executing the current effect.

import React, { useState, useEffect } from 'react';

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    // Specify how to clean up after this effect:
    return function cleanup() {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';

Now suppose that props.friend.id is updating 100 - > 200 - > 300
We don't need specific code to handle the update logic, because useEffect handles it by default. It cleans up the previous effect before calling a new effect. This is based on BUG s that would not have been generated previously due to Props changes.

// Mount with { friend: { id: 100 } } props
ChatAPI.subscribeToFriendStatus(100, handleStatusChange);     // Run the first effect

// Update with { friend: { id: 200 } } props
ChatAPI.unsubscribeFromFriendStatus(100, handleStatusChange); // Clear up the last effect
ChatAPI.subscribeToFriendStatus(200, handleStatusChange);     // Run the next effect

// Update with { friend: { id: 300 } } props
ChatAPI.unsubscribeFromFriendStatus(200, handleStatusChange); // Clear up the last effect
ChatAPI.subscribeToFriendStatus(300, handleStatusChange);     // Run the next effect

// Unmount
ChatAPI.unsubscribeFromFriendStatus(300, handleStatusChange); // Clear the last effect

Extension: If you want to perform an effect that runs only once (only when components are mounted and unloaded), you can pass an empty array ([]) as the second parameter. This tells React that your effect does not depend on any value in props or state, so it never needs to be repeated. This is not a special case -- it still follows the way dependency arrays work.

Reference

Tags: Front-end React

Posted on Wed, 09 Oct 2019 05:33:42 -0700 by rajns