facebook / react

The library for web and native user interfaces.
https://react.dev
MIT License
227.78k stars 46.5k forks source link

Bug: useFormStatus pending state is reset when component state is updated #30368

Open jatwood opened 2 months ago

jatwood commented 2 months ago

If you have a component that relies on the pending return value of useFormStatus, the pending state will incorrectly reset to false if the component is updated due to a useState update. This does not happen if the useState hook is placed in a child component.

React version: 19.0.0-rc-512b09b2-20240718

Steps To Reproduce

codesandbox.io/p/sandbox/react-useformstatus-pending-reset-on-unrelated-state-update-m59zw8

  1. Create a react app that uses form actions (this uses NextJS starter code)
  2. Have the form action delay for a set period of time before resolving
  3. Create a child component that uses useFormStatus().pending. Have the component also use a useState hook that updates on an interval using useEffect.
  4. Place this child component as a child of the <form /> element
  5. Verify that useFormStatus().pending is reset to false as soon as a call to setState happns

Example component


export default function SubmissionState() {
  const formStatus = useFormStatus();
  const [counter, setCounter] = useState(0);
  useEffect(() => {
    const id = setTimeout(() => {
      if (!formStatus.pending) {
        setCounter(0);
        return;
      }
      setCounter(counter + 1);
    }, 1000);
    return () => clearTimeout(id);
  }, [counter, formStatus.pending]);
  return (
    <div>
      {formStatus.pending ? `Pending for ${counter} seconds` : "Not pending"}
    </div>
  );
}

this example works as expected, useFormStatus().pending state matches the server actions state

function SubmissionStateBody({ pending }: { pending: boolean }) {
  const [counter, setCounter] = useState(0);
  useEffect(() => {
    if (!pending) {
      setCounter(0);
      return;
    }
    const id = setTimeout(() => {
      setCounter(counter + 1);
    }, 1000);
    return () => clearTimeout(id);
  }, [counter, pending]);
  return (
    <div>{pending ? `Pending for ${counter} seconds` : "Not pending"}</div>
  );
}

export default function SubmissionStateCorrect() {
  const formStatus = useFormStatus();
  return <SubmissionStateBody pending={formStatus.pending} />;
}

Link to code example:

Github repro using nextjs starter template

The current behavior

The UI shows the submission as pending for the length of the server action.

The expected behavior

The UI shows the submission as not-pending as soon as the counter is updated.

Aman37773 commented 2 months ago

Basically, if you want it to show status as not-pending once counter is updated then create another state variable(lets say pendinggg) whose initial value is same as that of pending and you immediately have to set pendinggg as false once counter is updated and inside useeffect have a condition that once pending is false then not to re-run the useeffect.

import React, { useState, useEffect } from 'react'; const useFormStatus = () => { return { pending: true }; // Example status, replace with actual implementation }; function SubmissionStateBody({pending}) { const [counter, setCounter] = useState(0); const [localPending, setLocalPending] = useState(pending); useEffect(() => { let id; if (localPending) { id = setTimeout(() => { setCounter((prevCounter) => prevCounter + 1); setLocalPending(false); // Stop showing pending as soon as the counter updates }, 1000); } else { setCounter(0); }

return () => clearTimeout(id);

}, [localPending, counter]);

return

{localPending ? Pending for ${counter} seconds : 'Not pending'}
; }

export default function SubmissionStateCorrect() { const formStatus = useFormStatus(); return ; }
thats the code here once counter updates, status becomes not pending..

jatwood commented 2 months ago

@Aman37773 This issue is about a buguseFormStatus which is a hook in react-dom.

eps1lon commented 2 months ago

Can be reproduced with latest RC and just Client Actions: https://codesandbox.io/p/sandbox/react-useformstatus-pending-reset-on-unrelated-state-update-m59zw8

ujshaikh commented 1 month ago

@eps1lon I would like to pick this bug, can you please assign it to me?

eps1lon commented 1 month ago

@ujshaikh We don't assign people to issues since they might stop working on the issue and then it looks like the issue is still being worked on. Assigning issues hasn't helped us manage issues. We just recommend to people start working on a bug immediately.