3

Consider the code below:

function Item({name, _key})
{
   console.log('rendering Item')
   const [updatingName, setUpdatingName] = useState(false);
   const nameInputElement = useRef();

   useEffect(() => {
      if (updatingName) {
         nameInputElement.current.focus();
      }
   }, [updatingName]);

   function onUpdateClick() {
      setUpdatingName(true);
   }

   function onCancelClick() {
      setUpdatingName(false);
   }

   return (
      <div>
         <input ref={nameInputElement} type="text" defaultValue={name} name="name"
         disabled={!updatingName} />

         {!updatingName
         ? <>
            <button key={1} type="button" onClick={onUpdateClick}>Update</button>
            <button key={2} type="submit" name="delete" value={_key}>Remove</button>
         </>
         : <>
            <button key={3} type="submit" name="update" onClick={(e) => {setUpdatingName(false)}}>Save</button>
            <button key={4} type="button" onClick={onCancelClick}>Cancel</button>
         </>}
      </div>
   )
}

function ItemList({title})
{
   return <>
      <h1>{title}</h1>
      <form method="post" onSubmit={(e) => {console.log('submitting');e.preventDefault()}}>
         <Item name={'small'} _key={0} />
      </form>
   </>
}

export default ItemList;

The problem that I am facing is the click of the Save button. When it's clicked, as you can see, I trigger a state change. But at the same time, I also want the button to cause the underlying <form>'s submission.

(To check whether the form is submitted, I've prevented its default submit mechanism and instead gone with a simple log.)

However, it seems to be the case that when the state change is performed from within the onClick handler of the Save button, it ceases to submit the form. If I remove the state change from within the handler, it then does submit the form.

Why is this happening?

Live CodeSandbox demo

3
  • If you apply onClick on the save button then it will trigger onClick rather than onSubmit of form. One simple thing you can do is don't apply a Onclick on Save button. Add setUpdatingName(false) inside form onSubmit event Commented Dec 11, 2022 at 13:14
  • 1
    it still seems bit weird that it gets cancelled because the button gets removed asynchronously, so if you like you can even try file a github issue, to make sure this is not react bug. Commented Dec 11, 2022 at 13:35
  • 1
    Yeah that can be a good point @GiorgiMoniava Commented Dec 11, 2022 at 13:35

1 Answer 1

2

When you call setUpdatingName(false) in save button's click handler, the button is removed from the DOM before submitting. You can add the logic for showing the buttons in ItemList, like below:

function ItemList({ title }) {
  const [updatingName, setUpdatingName] = useState(false);
  return (
    <>
      <h1>{title}</h1>
      <form
        method="post"
        onSubmit={(e) => {
          e.preventDefault();
          setUpdatingName(false);
          console.log("submitting");
        }}
      >
        <Item
          name={"small"}
          _key={0}
          updatingName={updatingName}
          setUpdatingName={setUpdatingName}
        />
      </form>
    </>
  );
}

export default ItemList;
function Item({ name, _key, updatingName, setUpdatingName }) {
  console.log("rendering Item");

  const nameInputElement = useRef();

  useEffect(() => {
    if (updatingName) {
      nameInputElement.current.focus();
    }
  }, [updatingName]);

  function onUpdateClick() {
    setUpdatingName(true);
  }

  function onCancelClick() {
    setUpdatingName(false);
  }

  return (
    <div>
      <input
        ref={nameInputElement}
        type="text"
        defaultValue={name}
        name="name"
        disabled={!updatingName}
      />

      {!updatingName ? (
        <>
          <button key={1} type="button" onClick={onUpdateClick}>
            Update
          </button>
          <button key={2} type="submit" name="delete" value={_key}>
            Remove
          </button>
        </>
      ) : (
        <>
          <button key={3} type="submit" name="update">
            Save
          </button>
          <button key={4} type="button" onClick={onCancelClick}>
            Cancel
          </button>
        </>
      )}
    </div>
  );
}

Edit angry-fast-upe615

Also, you could use useTransition to ask React to delay the state update, so the submission happens first:

function Item({ name, _key }) {
  console.log("rendering Item");
  const [isPending, startTransition] = useTransition();
  const [updatingName, setUpdatingName] = useState(false);
  const nameInputElement = useRef();

  useEffect(() => {
    if (updatingName) {
      nameInputElement.current.focus();
    }
  }, [updatingName]);

  function onUpdateClick() {
    setUpdatingName(true);
  }

  function onCancelClick() {
    setUpdatingName(false);
  }

  return (
    <div>
      <input
        ref={nameInputElement}
        type="text"
        defaultValue={name}
        name="name"
        disabled={!updatingName}
      />

      {!updatingName ? (
        <>
          <button key={1} type="button" onClick={onUpdateClick}>
            Update
          </button>
          <button key={2} type="submit" name="delete" value={_key}>
            Remove
          </button>
        </>
      ) : (
        <>
          <button
            key={3}
            type="submit"
            name="update"
            onClick={(e) => {
              startTransition(() => setUpdatingName(false));
            }}
          >
            Save
          </button>
          <button key={4} type="button" onClick={onCancelClick}>
            Cancel
          </button>
        </>
      )}
    </div>
  );
}

Edit angry-fast-upe615

Sign up to request clarification or add additional context in comments.

6 Comments

Yeah I also noticed it happened because the button was being removed, but the button was being removed asynchronously, so it still seemed weird to me that submit was still cancelled.
Yeah, this seems to be the right explanation of the strange behavior. Thanks.
@yousoumar I find the asynchronous point in the first comment here quite reasonable. Why doesn't it matter?
@yousoumar In my comment I was just curious why the cancel happened despite the button removal being asynchronous. I didn't find that explanation in your edit.
@GiorgiMoniava maybe it's because React itself defers the submission of function until the desired state changes occur inside any of its children. Cuz as we know, React does tap into the internals of form behavior. But for a decisive explanation, I think I need to take this to the ReactJS repo.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.