5

AFAIK, in the React Function Component, one has to use useEffect for componentWillUnmount functionality like below:

useEffect(()=>{
  return console.log("component unmounting");
},[])

I am making a form page, and want to submit the progress when the user exits the page. So I am thinking of submitting inside the cleanup.

But if I don't put the formData state into the dependency array, the state will be stale during the cleanup.
And if I do put formData into the dependency array, the cleanup will run every time there is a change in formData.

How can I keep the state fresh, but run the cleanup only in the 'real unmount'?

const [formData, setFormData] = useState({
  input1: 'input1 default',
  input2: 'input2 default',
}); // track the form data


useEffect(()=>{
  return () => {
    axios.post(myFormSubmitURL, formData);
  }
}, []); // this will submit the stale data

useEffect(()=>{
  return () => {
    axios.post(myFormSubmitURL, formData);
  }
}, [formData]); // this will submit the data every time it changes
Hyunwoong Kang
  • 530
  • 2
  • 9

1 Answers1

7

You can use a react ref and additional effect to store a cache of your form state for use when the component unmounts.

const formDataRef = useRef();
const [formData, setFormData] = useState({
  input1: 'input1 default',
  input2: 'input2 default',
}); // track the form data


useEffect(()=>{
  return () => {
    axios.post(myFormSubmitURL, formDataRef.current); // <-- access current value
  }
}, []);

useEffect(()=>{
  formDataRef.current = { ...formData }; // <-- cache form updated data
}, [formData]);
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • Wow thanks very much!! I didn't realize that I can useRef's other than HTMLElements until this answer. I will accept the answer as soon as stackoverflow allows me to do it (it says to wait 5 minutes) – Hyunwoong Kang Jan 22 '21 at 06:28
  • 1
    @HyunwoongKang React ref's are basically just a bucket you can store a value in that will persist through component rerenders and remain stable. [useRef](https://reactjs.org/docs/hooks-reference.html#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." – Drew Reese Jan 22 '21 at 06:29
  • This is a great solution that I've used as well. However, I'd suggest putting the ref update together directly inside an update function for the state. That way, it's clear to future readers of your code what happens when formData updates; they don't have to find that useEffect. – Irv Katz Sep 28 '22 at 17:14
  • 1
    @IrvKatz I certainly wouldn't recommend that. State updater functions should be pure functions, and updating a React ref value would surely be considered an unexpected side-effect. If you absolutely ***must*** do this then my recommendation would be to ***overtly*** document the side-effect in the code so anyone reading knows it's "intentional". – Drew Reese Sep 28 '22 at 17:16
  • Ok, @DrewReese, I can see that. Perhaps better would be the take the approach of this solution: https://stackoverflow.com/a/71655197/14894260, which is to create a custom hook to do the mirroring of the state by the ref. – Irv Katz Sep 28 '22 at 17:22
  • 1
    @IrvKatz That looks much like the old `usePrevious` hook logic React used to recommend. Looks like they've updated the [docs](https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state) a bit regarding this pattern in the time since. – Drew Reese Sep 28 '22 at 17:28
  • @DrewReese What about if we change the second line of that custom hook to be a useEffect (as in your answer) instead? Would that make the custom hook useful in that it follows the recommended coding approach while documenting our intention that we are mirroring a state? – Irv Katz Sep 28 '22 at 18:42
  • 1
    @IrvKatz Yes, I believe that change would make the code fairly self-explanatory at that point. – Drew Reese Sep 28 '22 at 19:20