Skip to content

Commit f5ec691

Browse files
committed
#51 - Typing fix
1 parent e3e37f6 commit f5ec691

File tree

4 files changed

+80
-43
lines changed

4 files changed

+80
-43
lines changed

README.md

+42
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,48 @@ export default function MyForm() {
8282
}
8383
```
8484

85+
## File Upload example
86+
87+
```jsx
88+
import { type UploadHandler } from "@remix-run/node";
89+
90+
export const fileUploadHandler =
91+
(): UploadHandler =>
92+
async ({ data, filename }) => {
93+
const chunks = [];
94+
for await (const chunk of data) {
95+
chunks.push(chunk);
96+
}
97+
const buffer = Buffer.concat(chunks);
98+
// If there's no filename, it's a text field and we can return the value directly
99+
if (!filename) {
100+
const textDecoder = new TextDecoder();
101+
return textDecoder.decode(buffer);
102+
}
103+
104+
return new File([buffer], filename, { type: "image/jpeg" });
105+
};
106+
107+
export const action = async ({ request }: ActionFunctionArgs) => {
108+
// use the upload handler to parse the file
109+
const formData = await unstable_parseMultipartFormData(
110+
request,
111+
fileUploadHandler(),
112+
);
113+
// The file will be there
114+
console.log(formData.get("file"));
115+
// validate the form data
116+
const { errors, data } = await validateFormData(formData, resolver);
117+
if (errors) {
118+
return json(errors, {
119+
status: 422,
120+
});
121+
}
122+
return json({ result: "success" });
123+
};
124+
125+
```
126+
85127
## Fetcher usage
86128

87129
You can pass in a fetcher as an optional prop and `useRemixForm` will use that fetcher to submit the data and read the errors instead of the default behavior. For more info see the docs on `useRemixForm` below.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "remix-hook-form",
3-
"version": "3.2.0",
3+
"version": "3.2.1",
44
"description": "Utility wrapper around react-hook-form for use with Remix.run",
55
"type": "module",
66
"main": "./dist/index.cjs",

src/testing-app/app/routes/_index.tsx

+31-42
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
type ActionFunctionArgs,
55
unstable_parseMultipartFormData,
66
LoaderFunctionArgs,
7+
type UploadHandler,
78
} from "@remix-run/node";
89
import { Form, useFetcher } from "@remix-run/react";
910
import {
@@ -31,23 +32,26 @@ const ACCEPTED_IMAGE_TYPES = [
3132
"image/png",
3233
"image/webp",
3334
];
35+
export const fileUploadHandler =
36+
(): UploadHandler =>
37+
async ({ data, filename }) => {
38+
const chunks = [];
39+
console.log("udje?", filename);
40+
for await (const chunk of data) {
41+
chunks.push(chunk);
42+
}
43+
const buffer = Buffer.concat(chunks);
44+
// If there's no filename, it's a text field and we can return the value directly
45+
if (!filename) {
46+
const textDecoder = new TextDecoder();
47+
return textDecoder.decode(buffer);
48+
}
49+
50+
return new File([buffer], filename, { type: "image/jpeg" });
51+
};
52+
3453
export const patientBaseSchema = generateObjectSchema({
35-
id: stringOptional(),
36-
firstName: stringRequired(),
37-
lastName: stringRequired(),
38-
primaryCareProvider: stringOptional(),
39-
dateOfBirth: dateOfBirthRequired(),
40-
email: emailOptional(),
41-
hasThirdPartyCoverage: booleanOptional(),
42-
isForeignCitizen: booleanOptional(),
43-
allergies: z.array(stringRequired()).optional(),
44-
healthCardNumber: stringOptional(),
45-
hasHealthCardNumber: booleanRequired(),
46-
city: stringRequired(),
47-
province: stringRequired(),
48-
street: stringRequired(),
49-
postalCode: stringRequired(),
50-
healthCardProvince: stringRequired(),
54+
file: z.any().optional(),
5155
});
5256
const FormDataZodSchema = z.object({
5357
email: z.string().trim().nonempty("validation.required"),
@@ -61,15 +65,15 @@ type FormData = z.infer<typeof patientBaseSchema>;
6165
const resolver = zodResolver(patientBaseSchema);
6266
export const loader = ({ request }: LoaderFunctionArgs) => {
6367
const data = getFormDataFromSearchParams(request);
64-
console.log("loader", data);
6568
return json({ result: "success" });
6669
};
6770
export const action = async ({ request }: ActionFunctionArgs) => {
68-
const { data, errors, receivedValues } = await getValidatedFormData(
71+
const formData = await unstable_parseMultipartFormData(
6972
request,
70-
resolver,
73+
fileUploadHandler(),
7174
);
72-
console.log("action", data, errors, receivedValues);
75+
console.log(formData.get("file"));
76+
const { errors, data } = await validateFormData(formData, resolver);
7377
if (errors) {
7478
return json(errors, {
7579
status: 422,
@@ -84,39 +88,24 @@ export default function Index() {
8488
resolver,
8589
fetcher,
8690
defaultValues: {
87-
firstName: "a",
88-
lastName: "t",
89-
primaryCareProvider: "",
90-
dateOfBirth: new Date("1997-09-05T00:00:00.000Z"),
91-
92-
email: "",
93-
94-
hasThirdPartyCoverage: false,
95-
isForeignCitizen: false,
96-
allergies: [],
97-
city: "Sarajevo",
98-
street: "Radenka Abazovića",
99-
province: "ON",
100-
postalCode: "a5t 5a5",
101-
hasHealthCardNumber: true,
102-
healthCardNumber: "5555555555",
103-
healthCardProvince: "ON",
91+
file: undefined,
92+
},
93+
submitData: {
94+
test: "test",
10495
},
10596
submitConfig: {
10697
method: "POST",
98+
encType: "multipart/form-data",
10799
},
108100
});
109101
const { register, handleSubmit, formState, watch, reset } = methods;
110-
console.log(formState);
111102

112103
return (
113104
<RemixFormProvider {...methods}>
114105
<p>Add a thing...</p>
115106

116-
<Form method="post" onSubmit={handleSubmit}>
117-
<div className="flex flex-col gap-2">
118-
<input type="text" {...register("email")} />
119-
</div>
107+
<Form method="post" encType="multipart/form-data" onSubmit={handleSubmit}>
108+
<input type="file" {...register("file")} />
120109
<div>
121110
<button type="submit" className="button">
122111
Add

src/utilities/index.ts

+6
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,12 @@ export const createFormData = <T extends FieldValues>(data: T): FormData => {
153153
return formData;
154154
}
155155
Object.entries(data).map(([key, value]) => {
156+
if (value instanceof FileList) {
157+
for (let i = 0; i < value.length; i++) {
158+
formData.append(key, value[i]);
159+
}
160+
return;
161+
}
156162
if (value instanceof File || value instanceof Blob) {
157163
formData.append(key, value);
158164
} else formData.append(key, JSON.stringify(value));

0 commit comments

Comments
 (0)