3

If I return a function from useEffect I can be sure that that function will run when a component unmounts. But React seems to wipe the local state before it calls my unmounting function.

Consider this:

function Component () {

  const [setting, setSetting] = useState(false)

  useEffect(() => {

    setSetting(true)

    // This should be called when unmounting component
    return () => {

      console.log('Send setting to server before component is unmounted')
      console.log(setting) // false (expecting setting to be true)
      
    }
  }, [])
  
  return (
    <p>Setting is: {setting ? 'true' : 'false'}</p>
  )
}

Can anyone confirm that the expected behaviour is that the components state should be wiped? And, if that is the correct behaviour, how does one go about firing off the current component state to a server just before the component is unmounted?

To give some context, I'm debouncing a post request to a server in order to avoid firing it every time the user changes a setting. The debouncing works nicely, but I need a way to fire the request once a user navigates away from the page, as the queued debouncing method will no longer fire from the unmounted component.

shennan
  • 10,798
  • 5
  • 44
  • 79

2 Answers2

5

It's not that React "wipes out the state value", it's that you have closure on setting value (the value on-mount).

To get expected behavior you should use a ref and another useEffect to keep it up to date.

function Component() {
  const [setting, setSetting] = useState(false);

  const settingRef = useRef(setting);

  // Keep the value up to date
  // Use a ref to sync the value with component's life time
  useEffect(() => {
    settingRef.current = setting;
  }, [setting])
  

  // Execute a callback on unmount.
  // No closure on state value.
  useEffect(() => {
    const setting = settingRef.current;
    return () => {
      console.log(setting);
    };
  }, []);

  return <p>Setting is: {setting ? "true" : "false"}</p>;
}
Dennis Vash
  • 50,196
  • 9
  • 100
  • 118
  • "value is clearly being reset before the React component is unmounted" - That's not true. DO you have a reproducible example? Here is a code for you to play with: https://codesandbox.io/s/remove-closure-from-state-on-unmount-erqpe?file=/index.js – Dennis Vash Dec 14 '20 at 14:28
  • You're right, the `useEffect` is obviously only running once and not running on changes to `setting` so is effectively a closure. It's just a shame that the solution of using a ref is so un-elegant. Thanks for your contribution. – shennan Dec 14 '20 at 14:39
  • I dont know what elegant solution means, but in React thats how you make it work. If by elegant you mean short, just put it in a custom hook – Dennis Vash Dec 14 '20 at 14:49
  • There is certainly an elegance to working code. So, thank you. :-) – shennan Dec 14 '20 at 14:50
0

I have faced same issue, resolved by below code,

<TextField
            required
            id="title"
            name="title"
            label="Course Title"
            fullWidth
            autoComplete="given-name"
            variant="standard"
            inputRef={titleRef}
            value={title}
            onChange={(event) => setTitle(event.target.value)}
          />



  const [title, setTitle] = React.useState(courseDetails.title);

  const titleRef = React.useRef(title);

  React.useEffect(() => {
    const titleData = titleRef.current;
    console.log(titleRef.current + "@@@@@");
    return () => {
      console.log(titleData.value + "****");
    };
  }, []);
Nagesha S
  • 53
  • 5