skip to Main Content

I have a very simple React application that is exhibiting odd behavior:

import { useState } from 'react'

export function App(props) {
  const [ready, setReady] = useState(false)

  function handleSubmit(e) {
    e.preventDefault()
    alert("Submitted!")
  }

  return (
    <form onSubmit={handleSubmit}>
      { ready ?
      <button type="submit">Submit</button> :
      <button type="button" onClick={() => setReady(true)}>Next</button> }
    </form>
  )
}

https://playcode.io/1741452

When I click the "Next" button, I expect the "Submit" button to appear but instead the form’s onSubmit handler is triggered! What is causing this to occur, since the "Submit" button is never clicked?

5

Answers


  1. Chosen as BEST ANSWER

    I did some digging and found the source of the issue. (This page was helpful in explaining things.)

    It turns out that rendering the same component in the same position in the UI tree causes React to reuse the underlying DOM element. So in the example above, the <button> element is reused when re-rendered after ready is set to true.

    The correct way to avoid this is to set the key attribute on the two <button> elements:

    // ...
    { ready ?
      <button key="submit" type="submit">Submit</button> :
      <button key="next" type="button" onClick={() => setReady(true)}>Next</button> }
    // ...
    

    With this change, the expected behavior is observed.

    Incidentally, this is why the key attribute is required when rendering a list of elements (using .map() for example) — to ensure that each item maintains correct state when items are added or removed.


  2. Update your button as Below:

    <button type="button" onClick={(e) => {
      e.preventDefault()
      setReady(true)
    }}>Next</button>
    

    Need to set "e.preventDefault()" for each button event.

    Login or Signup to reply.
  3. You can also use visibility CSS to do the same, I am not sure why this happens, but this also solves your issue!

    import { useState } from 'react'
    
    export function App(props) {
      const [ready, setReady] = useState(false)
    
      function handleSubmit(e) {
        e.preventDefault()
        alert("Submitted!")
      }
    
      return (
        <form onSubmit={handleSubmit}> 
          <button type="submit" style={{visibility: ready ? 'visible' : 'hidden'}}>Submit</button> :
          <button type="button" onClick={() => setReady(true)}>Next</button>
        </form>
      )
    }
    

    play code

    Login or Signup to reply.
  4. You can edit the code to bring out the Submit button from the form and conditionally display the next button based on the value of ready state:

    import { useState } from 'react';
    
    export function App(props) {
      const [ready, setReady] = useState(false);
    
      function handleSubmit(e) {
        e.preventDefault();
        alert("Submitted!");
      }
    
      return (
        <>
          <form onSubmit={handleSubmit}>
            {ready ? <button type="submit">Submit</button> : null}
          </form>
          {!ready && <button type="button" onClick={() => setReady(true)}>Next</button>}
        </>
      );
    }
    
    Login or Signup to reply.
  5. You need to add preventDefault to the Next button as well.

    Here is a working example.

    https://codesandbox.io/p/sandbox/form-submission-forked-r3s3cl?file=%2Fsrc%2FApp.js%3A2%2C1-32%2C1

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search