Skip to content

Commit

Permalink
Merge pull request #112 from forge42dev/undefined-handling
Browse files Browse the repository at this point in the history
fix for undefined handling
  • Loading branch information
AlemTuzlak authored Sep 17, 2024
2 parents 6d1296b + 61951f0 commit 30e0787
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 55 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,4 @@ dist
.tern-port

/build
.history
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "remix-hook-form",
"version": "5.0.4",
"version": "5.1.0",
"description": "Utility wrapper around react-hook-form for use with Remix.run",
"type": "module",
"main": "./dist/index.cjs",
Expand Down
2 changes: 1 addition & 1 deletion src/testing-app/app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@ function App() {
</html>
);
}
export default withDevTools(App);
export default App;
55 changes: 17 additions & 38 deletions src/testing-app/app/routes/_index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
json,
type ActionFunctionArgs,
unstable_parseMultipartFormData,
LoaderFunctionArgs,
type LoaderFunctionArgs,
type UploadHandler,
} from "@remix-run/node";
import { Form, useFetcher } from "@remix-run/react";
Expand All @@ -17,22 +17,7 @@ import {
RemixFormProvider,
} from "remix-hook-form";
import { z } from "zod";
import {
generateObjectSchema,
stringOptional,
stringRequired,
dateOfBirthRequired,
emailOptional,
booleanOptional,
booleanRequired,
} from "~/zod";
const MAX_FILE_SIZE = 500000;
const ACCEPTED_IMAGE_TYPES = [
"image/jpeg",
"image/jpg",
"image/png",
"image/webp",
];

export const fileUploadHandler =
(): UploadHandler =>
async ({ data, filename }) => {
Expand All @@ -51,30 +36,26 @@ export const fileUploadHandler =
return new File([buffer], filename, { type: "image/jpeg" });
};

export const patientBaseSchema = generateObjectSchema({
file: z.any().optional(),
});
const FormDataZodSchema = z.object({
email: z.string().trim().nonempty("validation.required"),
password: z.string().trim().nonempty("validation.required"),
number: z.number({ coerce: true }).int().positive(),
redirectTo: z.string().optional(),
boolean: z.boolean().optional(),
date: z.date().or(z.string()),
null: z.null(),
});

type FormData = z.infer<typeof patientBaseSchema>;
type SchemaFormData = z.infer<typeof FormDataZodSchema>;

const resolver = zodResolver(patientBaseSchema);
const resolver = zodResolver(FormDataZodSchema);
export const loader = ({ request }: LoaderFunctionArgs) => {
const data = getFormDataFromSearchParams(request);
return json({ result: "success" });
};
export const action = async ({ request }: ActionFunctionArgs) => {
const formData = await unstable_parseMultipartFormData(
request,
fileUploadHandler(),
);
console.log(formData.get("file"));
const { errors, data } = await getValidatedFormData(formData, resolver);
const { errors, data } = await getValidatedFormData(request, resolver);
console.log(data, errors);
if (errors) {
return json(errors, {
status: 422,
Expand All @@ -89,28 +70,26 @@ export default function Index() {
resolver,
fetcher,
defaultValues: {
file: undefined,
redirectTo: undefined,
number: 4,
email: "[email protected]",
password: "test",
date: new Date(),
boolean: true,
null: null,
},
submitData: {
test: "test",
},
});
const { register, handleSubmit, formState, watch, setError } = methods;
useEffect(() => {
setError("root.file", {
message: "File is required",
});
}, []);

console.log(formState.errors);
return (
<RemixFormProvider {...methods}>
<p>Add a thing...</p>

<Form method="post" encType="multipart/form-data" onSubmit={handleSubmit}>
<input type="file" {...register("file")} />
{formState.errors.file && (
<p className="error">{formState.errors.file.message}</p>
)}
<div>
<button type="submit" className="button">
Add
Expand Down
58 changes: 58 additions & 0 deletions src/utilities/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,30 @@ describe("createFormData", () => {
expect(formData.get("bool")).toEqual(data.bool.toString());
});

it("should create a FormData object with the provided data and remove undefined values", () => {
const data = {
name: "John Doe",
age: 30,
bool: true,
b: undefined,
a: null,
object: {
test: "1",
number: 2,
},
array: [1, 2, 3],
};
const formData = createFormData(data);

expect(formData.get("name")).toEqual(JSON.stringify(data.name));
expect(formData.get("age")).toEqual(data.age.toString());
expect(formData.get("object")).toEqual(JSON.stringify(data.object));
expect(formData.get("array")).toEqual(JSON.stringify(data.array));
expect(formData.get("bool")).toEqual(data.bool.toString());
expect(formData.get("b")).toEqual(null);
expect(formData.get("a")).toEqual("null");
});

it("should handle null data", () => {
const formData = createFormData(null as any);
expect(formData).toBeTruthy();
Expand Down Expand Up @@ -356,6 +380,40 @@ describe("createFormData", () => {
},
});
});

it("doesn't send undefined values to the backend but sends null values", async () => {
const formData = createFormData({
name: "123",
age: 30,
hobbies: ["Reading", "Writing", "Coding"],
boolean: true,
a: null,
b: undefined,
numbers: [1, 2, 3],
other: {
skills: ["testing", "testing"],
something: "else",
},
});
const request = new Request("http://localhost:3000", { method: "POST" });
const requestFormDataSpy = vi.spyOn(request, "formData");

requestFormDataSpy.mockResolvedValueOnce(formData);
const parsed = await parseFormData<typeof formData>(request);

expect(parsed).toStrictEqual({
name: "123",
age: 30,
hobbies: ["Reading", "Writing", "Coding"],
boolean: true,
a: null,
numbers: [1, 2, 3],
other: {
skills: ["testing", "testing"],
something: "else",
},
});
});
});

describe("getValidatedFormData", () => {
Expand Down
42 changes: 27 additions & 15 deletions src/utilities/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,29 +153,41 @@ export const createFormData = <T extends FieldValues>(
if (!data) {
return formData;
}
Object.entries(data).map(([key, value]) => {
for (const [key, value] of Object.entries(data)) {
// Skip undefined values
if (value === undefined) {
continue;
}
// Handle FileList
if (value instanceof FileList) {
for (let i = 0; i < value.length; i++) {
formData.append(key, value[i]);
}
return;
continue;
}
if (value instanceof File || value instanceof Blob) {
formData.append(key, value);
} else {
if (stringifyAll) {
formData.append(key, JSON.stringify(value));
} else {
if (typeof value === "string") {
formData.append(key, value);
} else if (value instanceof Date) {
formData.append(key, value.toISOString());
} else {
formData.append(key, JSON.stringify(value));
}
}
continue;
}
// Stringify all values if set
if (stringifyAll) {
formData.append(key, JSON.stringify(value));
continue;
}
// Handle strings
if (typeof value === "string") {
formData.append(key, value);
continue;
}
// Handle dates
if (value instanceof Date) {
formData.append(key, value.toISOString());
continue;
}
});
// Handle all the other values

formData.append(key, JSON.stringify(value));
}

return formData;
};
Expand Down

0 comments on commit 30e0787

Please sign in to comment.