ProgramGuru

Submitting Forms

Submitting Forms

A teacher presses Save attendance twice while waiting for confirmation. The screen needs a clear submission stage and must not pretend a failed save succeeded.

Form submission is the deliberate attempt to commit the entered data. A submit handler can prevent browser navigation, validate a snapshot of the draft, perform the save and represent idle, sending, success or error stages explicitly.

  1. Understand

    Handle a native form’s submit event.

  2. Build

    Represent sending and success as distinct stages.

  3. Check

    Keep request errors and duplicate submissions visible in the design.

A submission has stages

  1. Submit

    The teacher requests a save.

  2. Wait

    Disable the action while the simulated save finishes.

  3. Confirm

    Display the submitted student name on success.

Read this relationship alongside the complete code. This diagram explains the flow; it does not execute React.

Use the form’s event boundary

A form has built-in keyboard behavior: Enter in a text field can submit it, and a submit button also triggers submission. Handling onSubmit covers those paths in one place. event.preventDefault stops the default navigation for this client demonstration; it does not validate the data or save anything itself.

A status string expresses one stage at a time. sending and sent cannot both be the current status. The teacher’s submitted name is captured before waiting so the success message describes the accepted snapshot. The changing input draft should not silently rewrite which student an earlier request meant to save.

Make a simulated save visible

saveAttendance is fully defined in this file and resolves after a short timer. It simulates waiting, not a network backend or permanent storage. The handler ignores a duplicate request during sending, trims the draft, waits and then shows the captured name. The input is disabled while waiting to keep this demonstration’s workflow simple.

React · JavaScript with JSX

import { useState } from 'react';
function saveAttendance(name) {
  return new Promise(resolve => setTimeout(() => resolve(name), 150));
}
export default function App() {
  const [name, setName] = useState('Asha');
  const [status, setStatus] = useState('idle');
  const [savedName, setSavedName] = useState('');
  async function submit(event) {
    event.preventDefault();
    if (status === 'sending' || !name.trim()) return;
    const submittedName = name.trim();
    setStatus('sending');
    const result = await saveAttendance(submittedName);
    setSavedName(result); setStatus('sent');
  }
  return <main><h1>Submit attendance</h1><form onSubmit={submit}>
    <label>Student name <input required value={name} disabled={status === 'sending'} onChange={event => setName(event.target.value)} /></label>
    <button disabled={status === 'sending'}>{status === 'sending' ? 'Saving attendance' : 'Save attendance'}</button>
  </form><p role="status">{status === 'sent' ? 'Saved attendance for ' + savedName : status === 'sending' ? 'Waiting for save' : 'Ready to save'}</p></main>;
}

Expected screen and behavior

Initially Ready to save appears. Save attendance briefly shows Waiting for save, then Saved attendance for Asha. This is an in-memory simulation.

Read the submitted snapshot

React · Make a simulated save visible

import { useState } from 'react';
function saveAttendance(name) {
  return new Promise(resolve => setTimeout(() => resolve(name), 150));
}
export default function App() {
  const [name, setName] = useState('Asha');
  const [status, setStatus] = useState('idle');
  const [savedName, setSavedName] = useState('');
  async function submit(event) {
    event.preventDefault();
    if (status === 'sending' || !name.trim()) return;
    const submittedName = name.trim();
    setStatus('sending');
    const result = await saveAttendance(submittedName);
    setSavedName(result); setStatus('sent');
  }
  return <main><h1>Submit attendance</h1><form onSubmit={submit}>
    <label>Student name <input required value={name} disabled={status === 'sending'} onChange={event => setName(event.target.value)} /></label>
    <button disabled={status === 'sending'}>{status === 'sending' ? 'Saving attendance' : 'Save attendance'}</button>
  </form><p role="status">{status === 'sent' ? 'Saved attendance for ' + savedName : status === 'sending' ? 'Waiting for save' : 'Ready to save'}</p></main>;
}

A submission captures submittedName from the current render’s name. Setting sending requests a render with disabled controls and a waiting message. The asynchronous function pauses at await, but the browser can continue drawing and processing other tasks. The form does not freeze the whole page.

When the simulated save resolves, the continuation stores the captured result and sets sent. The next screen confirms Asha. A real request can reject, so its handler needs try and catch, an error stage and a way to retry. A disabled button helps prevent duplicate clicks; server-side idempotency is a separate concern for important writes.

Name what confirmation actually proves

This example proves only that the local promise resolved. It does not prove that a school database was updated. A real implementation needs a documented endpoint, method, body, authentication and response contract. Check response.ok before announcing success; receiving an HTTP response does not imply the server accepted the record.

Submission belongs in the event handler because it responds to a deliberate teacher action. An Effect watching name would save on every edit or lifecycle synchronization rather than on the intended submit. If the form uses React action APIs instead, learn their particular pending and error contracts rather than mixing the two models accidentally.

Try the school task

React · starting example

import { useState } from 'react';
function saveAttendance(name) {
  return new Promise(resolve => setTimeout(() => resolve(name), 150));
}
export default function App() {
  const [name, setName] = useState('Asha');
  const [status, setStatus] = useState('idle');
  const [savedName, setSavedName] = useState('');
  async function submit(event) {
    event.preventDefault();
    if (status === 'sending' || !name.trim()) return;
    const submittedName = name.trim();
    setStatus('sending');
    const result = await saveAttendance(submittedName);
    setSavedName(result); setStatus('sent');
  }
  return <main><h1>Submit attendance</h1><form onSubmit={submit}>
    <label>Student name <input required value={name} disabled={status === 'sending'} onChange={event => setName(event.target.value)} /></label>
    <button disabled={status === 'sending'}>{status === 'sending' ? 'Saving attendance' : 'Save attendance'}</button>
  </form><p role="status">{status === 'sent' ? 'Saved attendance for ' + savedName : status === 'sending' ? 'Waiting for save' : 'Ready to save'}</p></main>;
}

Make a complete simulation that can fail when the student name is Missing. Show School record not found, allow editing and retrying, and clear the error on a new request. Keep the draft after failure so the teacher can correct it.

Worked answer

The answer defines both success and failure outcomes locally. The catch block sets an error stage and message, while a later attempt returns to sending. Entering Asha after the failed Missing attempt and submitting again demonstrates a recoverable form instead of a permanently blocked page.

React · JavaScript with JSX

import { useState } from 'react';
async function save(name) {
  await new Promise(resolve => setTimeout(resolve, 100));
  if (name === 'Missing') throw new Error('School record not found');
  return name;
}
export default function App() {
  const [name, setName] = useState('Missing');
  const [status, setStatus] = useState('idle');
  const [message, setMessage] = useState('Ready');
  async function submit(event) {
    event.preventDefault();
    if (status === 'sending' || !name.trim()) return;
    setStatus('sending'); setMessage('Saving');
    try { const saved = await save(name.trim()); setStatus('sent'); setMessage('Saved ' + saved); }
    catch (error) { setStatus('error'); setMessage(error.message); }
  }
  return <main><h1>Recoverable save</h1><form onSubmit={submit}>
    <label>Student name <input required disabled={status === 'sending'} value={name} onChange={event => setName(event.target.value)} /></label>
    <button disabled={status === 'sending'}>Save student</button>
  </form><p role="status">{message}</p></main>;
}

Expected screen and behavior

Saving Missing shows School record not found. Replace it with Asha and save again to show Saved Asha. No actual backend is contacted.

Mistakes and boundaries

Buttons inside forms submit by default. Give a separate Cancel or Add row button type="button" when it should not submit. Handle success based on the actual save result and leave recoverable drafts intact after failure.

A local pending flag is not a guarantee against duplicate writes from other tabs, retries or reconnects. Important school operations need an appropriate server contract. Also decide what should happen if a form disappears before a write completes; stopping an HTTP request does not guarantee the server reversed a completed write.

Check your reasoning

React · Make a simulated save visible

import { useState } from 'react';
function saveAttendance(name) {
  return new Promise(resolve => setTimeout(() => resolve(name), 150));
}
export default function App() {
  const [name, setName] = useState('Asha');
  const [status, setStatus] = useState('idle');
  const [savedName, setSavedName] = useState('');
  async function submit(event) {
    event.preventDefault();
    if (status === 'sending' || !name.trim()) return;
    const submittedName = name.trim();
    setStatus('sending');
    const result = await saveAttendance(submittedName);
    setSavedName(result); setStatus('sent');
  }
  return <main><h1>Submit attendance</h1><form onSubmit={submit}>
    <label>Student name <input required value={name} disabled={status === 'sending'} onChange={event => setName(event.target.value)} /></label>
    <button disabled={status === 'sending'}>{status === 'sending' ? 'Saving attendance' : 'Save attendance'}</button>
  </form><p role="status">{status === 'sent' ? 'Saved attendance for ' + savedName : status === 'sending' ? 'Waiting for save' : 'Ready to save'}</p></main>;
}

Choose the answer that follows the data and the interaction, then read the explanation for each alternative.

Why is the save in onSubmit rather than an Effect that watches the name?
Choose one answer. You can try again.

Answer and explanation

The save is caused by a deliberate submit action, not by displaying the current name.

Correct. An event handler expresses the teacher’s request to commit this draft.

Carry the idea forward

Handle the native submit event, capture a draft snapshot and describe pending, success and failure honestly. Move to the rules of Hooks before extracting more stateful behavior into reusable functions.

Read the primary reference: React: form reference.

Reaching the recap and completing the quizzes are separate milestones.

    Next lesson: Rules of Hooks →