useEffect simulating componentWillUnmount does not return updated state

隐身守侯 提交于 2021-02-16 22:46:01

问题


I have a functional component that initializes a state with useState, then this state is changed via an input field.

I then have a useEffect hook simulating componentWillUnmount so that, before the component unmounts, the current, updated state is logged to the console. However, the initial state is logged instead of the current one.

Here is a simple representation of what I am trying to do (this is not my actual component):

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

const Input = () => {
    const [text, setText] = useState('aaa');

    useEffect(() => {
        return () => {
            console.log(text);
        }
    }, [])

    const onChange = (e) => {
        setText(e.target.value);
    };

    return (
        <div>
            <input type="text" value={text} onChange={onChange} />
        </div>
    )
}

export default Input;

I initialize the state as "initial." Then I use the input field to change the state, say I type in "new text." However, when the component in unmounted, "initial" is logged to the console instead of "new text."

Why does this happen? How can I access the current updated state on unmount?

Many thanks!

Edit:

Adding text to useEffect dependency array doesn’t solve my problem because in my real-world scenario, what I want to do is to fire an asynchronous action based on the current state, and it wouldn’t be efficient to do so everytime the “text” state changes.

I’m looking for a way to get the current state only right before the component unmounts.


回答1:


You've effectively memoized the initial state value, so when the component unmounts that value is what the returned function has enclosed in its scope.

Cleaning up an effect

The clean-up function runs before the component is removed from the UI to prevent memory leaks. Additionally, if a component renders multiple times (as they typically do), the previous effect is cleaned up before executing the next effect. In our example, this means a new subscription is created on every update. To avoid firing an effect on every update, refer to the next section.

In order to get the latest state when the cleanup function is called then you need to include text in the dependency array so the function is updated.

Effect hook docs

If you pass an empty array ([]), the props and state inside the effect will always have their initial values. While passing [] as the second argument is closer to the familiar componentDidMount and componentWillUnmount mental model, there are usually better solutions to avoid re-running effects too often.

This means the returned "cleanup" function still only accesses the previous render cycle's state and props.

EDIT

useRef

useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.

...

It’s handy for keeping any mutable value around similar to how you’d use instance fields in classes.

Using a ref will allow you to cache the current text reference that can be accessed within the cleanup function.

/EDIT

Component

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

const Input = () => {
  const [text, setText] = useState('aaa');

  // #1 ref to cache current text value
  const textRef = useRef(null);
  // #2 cache current text value
  textRef.current = text;

  useEffect(() => {
    console.log("Mounted", text);

    // #3 access ref to get current text value in cleanup
    return () => console.log("Unmounted", text, "textRef", textRef.current);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  useEffect(() => {
    console.log("current text", text);
    return () => {
      console.log("previous text", text);
    }
  }, [text])

  const onChange = (e) => {
    setText(e.target.value);
  };

  return (
    <div>
      <input type="text" value={text} onChange={onChange} />
    </div>
  )
}

export default Input;

With the console.log in the returned cleanup function you'll notice upon each change in the input the previous state is logged to console.

In this demo I've logged current state in the effect and previous state in the cleanup function. Note that the cleanup function logs first before the current log of the next render cycle.




回答2:


You should pass text to useEffect

useEffect(() => {
  return () => {
     console.log(text);
  };
}, [text]);


来源:https://stackoverflow.com/questions/60734531/useeffect-simulating-componentwillunmount-does-not-return-updated-state

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!