vercel / vercel/next-learn

Suggestions for adding instructions on how to retain previously entered valid data when form submission fails due to server-side validation errors.

Open
#845 5 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
4.8k
Forks
2.2k
PR merge metrics
No merged PRs in 30d

Description

One of the burning question after following Chapter 14 of adding server-side data validation is that: it would be really helpful if we do not ask user to refill the form from scratch when submission fails due to data violation, an example could be that say user had picked a customer and entered an amount but forget to pick a "state", and then click on "create invoice", instead of wiping out the whole form, and ask user to start from scratch, it would be much better if we can show (extremely helpful for beginners) how we can return the filled data back with server action function and use that to fill the form.

image

so something like this:

// under app\lib\actions.ts
export async function createInvoice(prevState: State, formData: FormData) {
  const validatedFields = CreateInvoice.safeParse({
    customerId: formData.get('customerId'),
    amount: formData.get('amount'),
    status: formData.get('status'),
  })

  if (!validatedFields.success) {
    return {
      errors: validatedFields.error.flatten().fieldErrors,
      message: 'Missing Fields. Failed to Create Invoice.',
      // Returning back previously entered data here
      prevState: {
        customerId: formData.get('customerId'),
        amount: formData.get('amount'),
        status: formData.get('status'),
      },
    }
  }
  // continue
}

Also, it seems that setting the previously picked customer correct in "select" when server action failed is very tricky, and it took very long time for me to get a version working with:

// under app\ui\invoices\create-form.tsx
const previousCustomer = customers.find(
    (c) => c.id === state?.prevState?.customerId
)

// after return statement
<select
  id="customer"
  name="customerId"
  className="peer block w-full cursor-pointer rounded-md border border-gray-200 py-2 pl-10 text-sm outline-2 placeholder:text-gray-500"
  defaultValue=""
  aria-describedby="customer-error"
>
  <option
    value={previousCustomer ? previousCustomer.id : ''}
    disabled={!previousCustomer}
  >
    {previousCustomer ? previousCustomer.name : 'Select a customer'}
  </option>
  {customers
    .filter((c) => c.id !== previousCustomer?.id)
    .map((customer) => (
      <option key={customer.id} value={customer.id}>
        {customer.name}
      </option>
    ))}
</select>

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading Chapter 14 and the form flow in app/lib/actions.ts and app/ui/invoices/create-form.tsx. Reproduce a server-side validation failure with a missing state, then determine how the tutorial should explain retaining entered values, including the selected customer. Done means the chapter clearly documents the behavior and its implementation path.

Written by the indexing model from the issue text.

Assessment

Tech stack
next.js, typescript
Domain
documentation
Issue type
Documentation
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.