forge42dev / remix-hook-form

Open source wrapper for react-hook-form aimed at Remix.run
MIT License
330 stars 27 forks source link

Improve `getValidatedFormData` type inference #61

Closed mintchkin closed 9 months ago

mintchkin commented 9 months ago

Description

Small update that should allow inferring the schema type from the resolver type when calling getValidatedFormData or validateFormData.

Partially fixes #55, though there's still an annoying upstream issue in @hookform/resolvers that causes zodResolver() to lack type inference as was called out in this comment (some other resolvers e.g. yupResolver should work fine)

Before:

const schema = z.object({ username: z.string(), password: z.string() });
const resolver = zodResolver(schema);

export const action = async ({ request }: ActionFunctionArgs) => {
  const data = await getValidatedFormData<z.infer<typeof schema>>(request, resolver);
  // ...
}

After:

const schema = z.object({ username: z.string(), password: z.string() });

// this annotation is still required for zod because of an upstream bug in @hookform/resolvers
const resolver: Resolver<z.infer<typeof schema>> = zodResolver(schema);

export const action = async ({ request }: ActionFunctionArgs) => {
  const data = await getValidatedFormData(request, resolver);
  // ...
}

Type of change

Please delete options that are not relevant.

Checklist:

mintchkin commented 9 months ago

fwiw, I also pushed a PR to @hookform/resolvers that would allow full inference for zod resolvers when it's coupled with this one

const schema = z.object({ username: z.string(), password: z.string() });
const resolver = zodResolver(schema);

export const action = async ({ request }: ActionFunctionArgs) => {
  // data is appropriately typed here
  const data = await getValidatedFormData(request, resolver);
  // ...
}
AlemTuzlak commented 9 months ago

@mintchkin thank you for this! So the generic is passed to the resolver and it should work as long as the upstream supports type inference?

mintchkin commented 9 months ago

@AlemTuzlak yeah exactly, the Resolver<> type in react-hook-form accepts a generic argument for the shape of the object it returns, so it's possible to go

const schema = z.object({ ... })                    // -> Schema<T>
const resolver = zodResolver(schema)                // -> Resolver<T>
const result = getValidatedFormData(req, resolver)  // -> Promise<T>

without losing any type information, as long as zodResolver() and getValidatedFormData() are good about forwarding the T generic along the chain. unfortunately they both drop it right now, but this PR fixes that for getValidatedFormData() and the upstream PR fixes it for zodResolver()

AlemTuzlak commented 9 months ago

@mintchkin awesome, thank you for this!