next-learn icon indicating copy to clipboard operation
next-learn copied to clipboard

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

Open eiffel205566 opened this issue 1 year ago • 5 comments

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>

eiffel205566 avatar Aug 24 '24 22:08 eiffel205566

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

dimklose avatar Aug 30 '24 08:08 dimklose

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?

eiffel205566 avatar Sep 01 '24 23:09 eiffel205566

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.

uxfed avatar Oct 13 '24 16:10 uxfed

Linked issue :

  • https://github.com/facebook/react/issues/29034

simonc56 avatar Oct 16 '24 17:10 simonc56

Previously this tutorial used useFormState here. With React 19 this hook got deprecated and replaced with useActionState - they made it sound like it was simply renamed but it seems to behave differently regards resetting the form after submission.

I found the same solution here - include the form values in state and use these as the defaultValue (rather than the value from the database).

DrMoshtael avatar Feb 02 '25 21:02 DrMoshtael