#Form action not showing errors

1 messages · Page 1 of 1 (latest)

rugged patrol
#

Hello - new to Remix, and looking to implement a simple subscription form. Successful path looks OK, but when I try to return a 400 for a validation error, the page will redirect to the form action path e.g. /newsletter/subscription (which only exports an action) instead of staying on page, and showing the errors. The errors are also not logging client side, but they are server side. Hoping someone can let me know what I did wrong.

Thanks!

#

subscribe component:

export function Subscribe({ cta, campaign, className }: Props) {
  const formData = useActionData<SubscriptionActionData>()
  console.log('actionData', formData)

  return (
    <Form
      id={`subscribe-form-${campaign}`}
      method="POST"
      action="/newsletter/subscription"
      className={cn('flex xs:flex-row flex-col gap-2', className)}
    >
      <Fieldset className="mt-0 flex-1 w-full xs:max-w-sm">
        <FieldGroup>
          <Field>
            <label htmlFor="email" className="sr-only">
              Email
            </label>
            <Input
              id="email"
              type="email"
              name="email"
              autoComplete="email"
              placeholder="Your email address"
            />
            {formData?.errors?.email && (
              <ErrorMessage>{formData.errors.email._errors[0]}</ErrorMessage>
            )}
          </Field>
        </FieldGroup>
      </Fieldset>
      <Button type="submit" color="indigo">
        {cta}
      </Button>
    </Form>
  )
}

routes/newsletter.subscription.ts

export type SubscriptionActionData = {
  errors: any
  //  ZodFormattedError<
  //   {
  //     email: string
  //   },
  //   string
  // >
}

// TODO: figure out folder routing
export async function action({ request }: ActionFunctionArgs) {
  const formData = await request.formData()
  const formObject = Object.fromEntries(formData.entries())

  const result = subscribeSchema.safeParse(formObject)

  if (!result.success) {
    console.error('Subscribe error:', result.error.format())
    return json({ errors: result.error.format() }, { status: 400 })
  }

  console.log('subscribe result', result)

  return redirect('/')
}
grave laurel
#

Try using the fetcher.Form component
You won't need to perform any redirect

rugged patrol
#

I do need to ultimately redirect on success

grave laurel
midnight crow
#

Pass navigate=false to remix Form component. It will use a fetcher instead of a navigation and won't redirect when submitting.

rugged patrol
#

thanks for the suggestions. navigate=false did work, but no errors show up (client side logging gives me an undefined for my const formData = useActionData<SubscriptionActionData>()). I assume this is why it's not working in the first place. hm wonder why my action is not returning the json back

abstract sage
#

In Remix, <Form action> by default will navigate to the route specified in action. That's why for form validation, it's best to omit action, so it will post to the current route. With <Form>, you then get the data via useActionData.

If you're using <Form action navigate=false>, Remix will use a fetcher behind the scenes and do the post to your action but will not navigate to it. However, if you want to access the action data, you can no longer use useActionData.

To get this data, include <Form fetcherKey='my-key'>, and create a fetcher const fetcher = useFetcher({ key: 'my-key' })

Then you can access that data from fetcher.data

You can also simply do:

const fetcher = useFetcher() // no explicit key
const data = fetcher.data
// fetcher.Form will use the fetcher you created without worrying about keys
return <fetcher.Form method="post" action="/some/action">...
#

The Remix API has been converging on Forms vs Fetchers lately, so it won't surprise me if they unify the API in a future version.

#

But general rule of thumb is, use <Form> for posts to current route (especially if you're returning validation errors).

Use <fetcher.Form> or useFetcher for posts to non-current routes. Especially for shared components that you can use from any route. Also, useful for "intent" actions. For example "Delete", "Like", etc. Where these are secondary actions on a given route.