A field-level error belongs to one input and appears next to it (fieldState.error, or errors.email). A form-level error belongs to the submission: the network failed, or the stock ran out. React Hook Form 351,018 keeps these under the root key, set with setError() once the server answers:
const onValid = async (form: CheckoutForm) => {
try {
const result = await placeOrder(form, useCart.getState().items);
if ('error' in result) { // the server said no: a form-level error
setError('root.server', { message: result.error });
return;
}
useCart.getState().clear();
setOrderId(result.orderId);
} catch {
// ...a network failure sets a root error too: "BookNest is unreachable. Try again."
}A banner above the fields renders errors.root?.server?.message. handleSubmit calls onValid only when the resolver passes, and isSubmitting stays true until its promise settles. Field errors clear as each field is fixed; root errors are cleared at the start of the next handleSubmit.