Skip to content

Commit

Permalink
fix: preserve files only on client (#490)
Browse files Browse the repository at this point in the history
  • Loading branch information
edmundhung authored Mar 7, 2024
1 parent 388a972 commit 7a19af5
Show file tree
Hide file tree
Showing 4 changed files with 527 additions and 49 deletions.
35 changes: 16 additions & 19 deletions packages/conform-dom/formdata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,22 +165,28 @@ export function isFile(obj: unknown): obj is File {
* Normalize value by removing empty object or array, empty string and null values
*/
export function normalize<Type extends Record<string, unknown>>(
value: Type | null,
): Type | null | undefined;
value: Type,
acceptFile?: boolean,
): Type | undefined;
export function normalize<Type extends Array<unknown>>(
value: Type | null,
): Type | null | undefined;
export function normalize(value: unknown): unknown | undefined;
value: Type,
acceptFile?: boolean,
): Type | undefined;
export function normalize(
value: unknown,
acceptFile?: boolean,
): unknown | undefined;
export function normalize<
Type extends Record<string, unknown> | Array<unknown>,
>(
value: Type | null,
): Record<string, unknown> | Array<unknown> | null | undefined {
value: Type,
acceptFile = true,
): Record<string, unknown> | Array<unknown> | undefined {
if (isPlainObject(value)) {
const obj = Object.keys(value)
.sort()
.reduce<Record<string, unknown>>((result, key) => {
const data = normalize(value[key]);
const data = normalize(value[key], acceptFile);

if (typeof data !== 'undefined') {
result[key] = data;
Expand All @@ -201,26 +207,17 @@ export function normalize<
return undefined;
}

return value.map(normalize);
return value.map((item) => normalize(item, acceptFile));
}

if (
(typeof value === 'string' && value === '') ||
value === null ||
(isFile(value) && value.size === 0)
(isFile(value) && (!acceptFile || value.size === 0))
) {
return;
}

// We will skip serializing file if the result is sent to the client
if (isFile(value)) {
return Object.assign(value, {
toJSON() {
return;
},
});
}

return value;
}

Expand Down
7 changes: 6 additions & 1 deletion packages/conform-dom/submission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,12 @@ export function replySubmission<FormError>(
return {
status: context.intent ? undefined : error ? 'error' : 'success',
intent: context.intent ? context.intent : undefined,
initialValue: normalize(context.payload) ?? {},
initialValue:
normalize(
context.payload,
// We can't serialize the file and send it back from the server, but we can preserve it in the client
typeof document !== 'undefined',
) ?? {},
error,
state: context.state,
fields: Array.from(context.fields),
Expand Down
51 changes: 49 additions & 2 deletions playground/app/routes/metadata.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,33 @@ const schema = z.object({
bookmarks.length,
'Bookmark URLs are repeated',
),
file: z.instanceof(File, { message: 'File is required' }),
files: z
.instanceof(File)
.array()
.min(1, 'At least 1 file is required')
.refine(
(files) => files.every((file) => file.type === 'application/json'),
'Only JSON file is accepted',
),
});

const getPrintableValue = (value: unknown) => {
if (typeof value === 'undefined') {
return;
}

return JSON.parse(
JSON.stringify(value, (key, value) => {
if (value instanceof File) {
return `${value.name} (${value.size} bytes)`;
}

return value;
}),
);
};

export async function loader({ request }: LoaderArgs) {
const url = new URL(request.url);

Expand Down Expand Up @@ -62,14 +87,14 @@ export default function Example() {
const bookmarks = fields.bookmarks.getFieldList();

return (
<Form method="post" {...getFormProps(form)}>
<Form method="post" {...getFormProps(form)} encType="multipart/form-data">
<Playground
title="Metadata"
result={{
form: {
status: form.status,
initialValue: form.initialValue,
value: form.value,
value: getPrintableValue(form.value),
dirty: form.dirty,
valid: form.valid,
errors: form.errors,
Expand Down Expand Up @@ -107,6 +132,22 @@ export default function Example() {
errors: bookmarks[1]?.errors,
allErrors: bookmarks[1]?.allErrors,
},
file: {
initialValue: fields.file.initialValue,
value: getPrintableValue(fields.file.value),
dirty: fields.file.dirty,
valid: fields.file.valid,
errors: fields.file.errors,
allErrors: fields.file.allErrors,
},
files: {
initialValue: fields.files.initialValue,
value: getPrintableValue(fields.files.value),
dirty: fields.files.dirty,
valid: fields.files.valid,
errors: fields.files.errors,
allErrors: fields.files.allErrors,
},
}}
>
<Field label="Title" meta={fields.title}>
Expand All @@ -128,6 +169,12 @@ export default function Example() {
</fieldset>
);
})}
<Field label="File" meta={fields.file}>
<input {...getInputProps(fields.file, { type: 'file' })} />
</Field>
<Field label="Files" meta={fields.files}>
<input {...getInputProps(fields.files, { type: 'file' })} multiple />
</Field>
</Playground>
</Form>
);
Expand Down
Loading

0 comments on commit 7a19af5

Please sign in to comment.