vercel / next-learn

Learn Next.js Starter Code
https://next-learn-dashboard.vercel.sh/
MIT License
3.71k stars 1.9k forks source link

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

Open eiffel205566 opened 1 month ago

eiffel205566 commented 1 month ago

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>
dimklose commented 1 month ago

State does lack 'prevState', so your example is not working. Maybe you could be so nice and add your whole solution to this problem.

eiffel205566 commented 1 month ago

State does lack 'prevState', so your example is not working. Maybe you could be so nice and add your whole solution to this problem.

image

did you type State accordingly?

uxfed commented 1 week ago

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... 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.

Thanks for posting this excellent solution! It is extremely helpful.

simonc56 commented 5 days ago

Linked issue :