Next.js Discord

Discord Forum

Best practices for validating form data in an action?

Answered
Dwarf Crocodile posted this in #help-forum
Open in Discord
Avatar
Dwarf CrocodileOP
A server action gets a FormData as an input, which is a list of objects. Are there any best practices on how to validate it? And also to catch/handle the respective errors in the form?
Answered by codekrafter
in actions.js:
'use server'

async function addItem(data) {
    const cartId = cookies().get('cartId')?.value
    const result = await saveToDb({ cartId, data })
    
    return result
}


in the client component
'use client'
...
import { addItem } from './actions'
...

export default function AddToCart({ productId }) {
  const [error,setError] = useState(null)
 
  return (
    <form onSubmit={(event) => addItem(...).then(r) => setError(r)}>
      <button type="submit">Add to Cart</button>
      <span class="error">{error}</span>
    </form>
  )
}
View full answer

34 Replies

Avatar
codekrafter
You may be able to use a library like this to use Zod for validation: https://www.npmjs.com/package/zod-form-data
Avatar
Dwarf CrocodileOP
My question is though, suppose you validate the data and throw an error. How can you show the user feedback that there was an error? Assuming you did something like: <form action={myAction} ...> and within myAction you throw an error if there's something wrong with the data, or authentication, etc. How do you bubble up that error properly to the interface? https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions#validation
Avatar
Sloth bear
If you use server actions from a client component, you can treat the server action as a promise, and chain a .then after it to catch the response.
Avatar
codekrafter
There is also this twitter thread that may help: https://twitter.com/dan_abramov/status/1654336219919048704
Avatar
Dwarf CrocodileOP
Thanks for the useful links. I think one thing that would make all this more clear is, how do you even handle a successful server action for a form submission? How can you pass data returned from the server action back to the form/component so it can display feedback? Not clear from the docs to me.
Avatar
Sloth bear
It works the same as a promise. You can return data directly from the server action. In the form action attribute, you can use .then to catch that returned data and use it.
Avatar
Dwarf CrocodileOP
@Sloth bear can you please direct me to an example using .then to catch the error? That is the specific part that I don't get.
Or using .then to catch any returned data. That seems really powerful and would solve my problem. Thank you.
Avatar
Dwarf CrocodileOP
That page has zero instances of .then 😕
Avatar
codekrafter
It has an await call which is functionally the same
Avatar
Dwarf CrocodileOP
Ah and if I inline the action inside a client component, I could do stuff like useState, etc.?
Avatar
codekrafter
If you call the server action from a client component, you could update state by awaiting (or using .then) the result
Avatar
Dwarf CrocodileOP
kind of like this?
export default function AddToCart({ productId }) {
  async function addItem(data) {
    'use server'
 
    const cartId = cookies().get('cartId')?.value
    const result = await saveToDb({ cartId, data })
    
    if (result.errors) {
        setError(result.errors[0])
    }
  }
 
  return (
    <form action={addItem}>
      <button type="submit">Add to Cart</button>
      <span class="error">{error}</span>
    </form>
  )
}
Avatar
codekrafter
You can’t have the set error call in the server action, you need it in the function that calls the server action
Which would require manually calling it instead of using a form action
Avatar
Dwarf CrocodileOP
How do you manually call it?
Like using onClick?
Avatar
codekrafter
I'll type up an example real quick
Avatar
Dwarf CrocodileOP
Incredibly grateful.
Avatar
codekrafter
in actions.js:
'use server'

async function addItem(data) {
    const cartId = cookies().get('cartId')?.value
    const result = await saveToDb({ cartId, data })
    
    return result
}


in the client component
'use client'
...
import { addItem } from './actions'
...

export default function AddToCart({ productId }) {
  const [error,setError] = useState(null)
 
  return (
    <form onSubmit={(event) => addItem(...).then(r) => setError(r)}>
      <button type="submit">Add to Cart</button>
      <span class="error">{error}</span>
    </form>
  )
}
Answer
Avatar
codekrafter
Very rough example, just make sure to only return serializable data
Avatar
Dwarf CrocodileOP
Really appreciate this @codekrafter 🙏
Avatar
codekrafter
happy to help! I think the whole server actions area is still pretty in-progress and experimental so some quirks are still being smoothed out and I am sure this will be a lot cleaner in the future
Avatar
Dwarf CrocodileOP
It is nice enough that I'm willing to give it a shot.
One last comment @codekrafter - the form data would be under event.target.inputNameHere.value? No way to just access FormData in that case right?
Avatar
codekrafter
You can construct FormData from a form element: https://developer.mozilla.org/en-US/docs/Web/API/FormData/FormData
Avatar
Dwarf CrocodileOP
pardon the ignorant question, how do I access the FormData from the example you sent?
Avatar
codekrafter
Should be something like new FormData(event.target)
Avatar
Dwarf CrocodileOP
🆗 thank you!
Avatar
Satin
Hey there! So this means that you can't show any errors without JS? So if JS is disabled and there is an error you don't see the feedback
Avatar
codekrafter
With that solution, yes. I am not sure if there is a way to display errors atm with progressive enhancement
Avatar
Satin
Damn, thanks for the reply