Development Notes: useRef gets the previous status and useEffect

Keywords: React Javascript

16. The pit in useeffect and the status before using useRef to record

1. Scene description

Usage when using useEffect hooks, we will add a dependency to it to complete some side effects when one or some dependencies change. The normal usage is as follows:

import React, { useEffect, useState } from "react";
import "./styles.css";

const Profile = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log("Number is added!");
  }, [count]);
    
  return (
    <>
      <p>Current weight: {count}</p>
      <button onClick={() => setCount(count+1)}>+1</button>
    </>
  );
};

export default Profile;

However, when the application scenario becomes more and more complex, we find two problems:

  • When the dependency is a variable of reference type (array), useEffect will execute again whether the dependency direction changes or not
  • When there are multiple variables in the dependency, the whole useEffect will be executed again if there is one change

Problem 1 Analysis:

import React, { useEffect, useRef } from "react";
import "./styles.css";

const Profile = () => {
  const [user, setUser] = React.useState({ name: "Alex", weight: 40 });

  React.useEffect(() => {
      console.log("You need to do exercise!");
  }, [user]);

  const gainWeight = () => {
    const newWeight = Math.random() >= 0.5 ? user.weight : user.weight + 1;
    setUser(user usage=> ({ ...user, weight: newWeight }));
  };

  return (
    <>
      <p>Current weight: {user.weight}</p>
      <button onClick={gainWeight}>Eat burger</button>
    </>
  );
};
//usage
export default Profile;

It can be found here that as long as you click the button, no matter whether the number is increased or not, the function in useEffect will be called. The root cause is that * * * useEffect itself determines whether the elements are the same by = = =, while in javascript:

[] === [] // false
{} === {} // false

Therefore, if it is a reference type, the method in the useEffect will be referenced regardless of whether a value changes.

Problem 2 Analysis:

import React, { useEffect, useState } from "react";
import "./styles.css";

const Profile = () => {
  const [count, setCount] = useState(0);
  const [simulate, setSimulate] = useState([]);

  useEffect(() => {
    console.log("Number is added!");
  }, [count, simulate]);

  const addList = () => {
    setSimulate(sim => [...sim, Math.random().toFixed(2)]);
  };

  const addNum = () => {
    setCount(Math.random() > 0.5 ? count + 1 : count);
  };

  return (
    <>
      <p>Current weight: {count}</p>
      <button onClick={addList}>Add list</button>
      <button onClick={addNum}>number + 1</button>
    </>
  );
};

export default Profile;

When simulate changes, it does call to print logs, but we may want to print only when number is increased (of course, count can only be used as a dependency here), but in some scenarios, count and simulate are coupled and cannot be separated. Here we discuss the use of this coupling scenario. Therefore, in order to solve this problem, it is necessary to know the value of count when updating the previous status. Obviously, in useEffect, we cannot get the previous value through the hook function.

2. Solutions

Problem 1: change dependency

For example, here we just want to monitor the change of weight. We can change the dependency to user.weight

import React, { useEffect, useRef } from "react";
import "./styles.css";

const Profile = () => {
  const [user, setUser] = React.useState({ name: "Alex", weight: 40 });
  const prevUser = usePrevious(user);

  React.useEffect(() => {
     console.log("You need to do exercise!");
  }, [user.weight]);

  const gainWeight = () => {
    const newWeight = Math.random() >= 0.5 ? user.weight : user.weight + 1;
    setUser(user => ({ ...user, weight: newWeight }));
  };

  return (
    <>
      <p>Current weight: {user.weight}</p>
      <button onClick={gainWeight}>Eat burger</button>
      <Test />
    </>
  );
};

export default Profile;

**For questions 1 and 2: * * get the previous status and update it after comparison

Therefore, the problem is how to get the previous state in the functional component. After referring to various articles, we come to the conclusion that using the function of useRef itself is to save the given data value through the internal closure, and can remember the last given value

An example of usePrevious

import React, { useEffect, useRef } from "react";
import Test from "./Test";
import "./styles.css";

const Profile = () => {
  const [user, setUser] = React.useState({ name: "Alex", weight: 40 });
  const prevUser = usePrevious(user);

  React.useEffect(() => {
    prevUser &&
      user.weight > prevUser.weight &&
      console.log("You need to do exercise!");
  }, [user]);

  const gainWeight = () => {
    const newWeight = Math.random() >= 0.5 ? user.weight : user.weight + 1;
    setUser(user => ({ ...user, weight: newWeight }));
  };

  return (
    <>
      <p>Current weight: {user.weight}</p>
      <button onClick={gainWeight}>Eat burger</button>
      <Test />
    </>
  );
};

export default Profile;

function usePrevious(value) {
  const ref = useRef();

  useEffect(() => {
    ref.current = value;
  }, [value]);

  return ref.current;
}
3. Reference articles

Introduction to useRef Hook : This article explains useRef and createRef as well as the usage of useRef!

73 original articles published, 6 praised, 20000 visitors+
Private letter follow

Posted by munchy on Mon, 20 Jan 2020 01:44:29 -0800