forge42dev / remix-hook-form

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

getValidatedFormData auto parse JSON cause error if value is JSON string #71

Closed anhdd-kuro closed 2 months ago

anhdd-kuro commented 7 months ago

Problem

Given this schema

export const FooSchema = z.object({
  title: z.string()
  number: z.string(),
  jsonArray: z.string()
})

which will pass validation on the client side

<input name="title" value="title" />
<input name="number" value="0" />
<input name="jsonArray" value="[]" />

If submit by form.submit() event or submit from Remix.useSubmit() which maybe equal with submit without JS ? It will fails when using getValidatedFormData because the types of number and jsonArray are not strings. This happens when the following line is executed:

getValidatedFormData(request, FooSchema)

This issue arises because the parseFormData function auto-parses all values due to the use of preserveStringified = false.

const data = isGet(request) ? getFormDataFromSearchParams(request) : await parseFormData(request);

var parseFormData = async (request, preserveStringified = false) => {
  const formData = request instanceof Request ? await request.formData() : request;
  return generateFormData(formData, preserveStringified);
};

Expected behavior

It should pass. Perhaps we can pass preserveStringified as the third parameter of getValidatedFormData? For now, I must use dispatchEvent(new Event("submit", { cancelable: true, bubbles: true })) as a work around

AlemTuzlak commented 7 months ago

@anhdd-kuro Will add an ability to pass this in wiht getValidatedFormData too.

SolomidHero commented 6 months ago

Encountered same issue. Work around also to use z.coerce.string() as schema of resolver. But it is not preferable action :(

upd: added new file with following definition to use such parameter:

import { FieldValues, Resolver } from "react-hook-form";
import { getFormDataFromSearchParams, parseFormData, validateFormData } from "remix-hook-form";

export const isGet = (request: Pick<Request, "method">) =>
  request.method === "GET" || request.method === "get";

export const getValidatedFormData = async <T extends FieldValues>(
  request: Request,
  resolver: Resolver<T>,
) => {
  const data = isGet(request)
    ? getFormDataFromSearchParams(request)
    : await parseFormData<T>(request, true); // <- just set it for true in my case

  const validatedOutput = await validateFormData<T>(data, resolver);
  return { ...validatedOutput, receivedValues: data };
};
AlemTuzlak commented 6 months ago

@anhdd-kuro @SolomidHero can you try with the latest version, I've added the third parameter to the getValidatedFormData

AlemTuzlak commented 2 months ago

this has been implemented already!