Перейти к основному содержимому

Загрузка файлов

Данная страница находится в процессе перевода. Временно доступна английская версия.

File uploads require special handling for autosave. This guide shows you how to implement it properly.

Important Considerations

Note: Form Guardian cannot directly save file objects to IndexedDB. However, you can:

  1. Save file metadata (name, size, type)
  2. Use FileReader API to save file data as base64 (for small files)
  3. Upload files immediately and save file URLs
  4. Use IndexedDB File API (for larger files)

Save File Metadata Only

For most cases, saving file metadata is sufficient:

import { useState } from 'react';
import { useFormAutosave } from '@form-guardian/react';

interface FileMetadata {
name: string;
size: number;
type: string;
lastModified: number;
}

function FileUploadForm() {
const [files, setFiles] = useState<FileMetadata[]>([]);
const [fileInputs, setFileInputs] = useState<File[]>([]);

const { formRef, restoreValues, clearDraft } = useFormAutosave(
'file-upload-form',
{ autoRestore: false }
);

useEffect(() => {
restoreValues(
(field, value) => {
if (field === 'files' && Array.isArray(value)) {
setFiles(value);
// Note: Actual File objects cannot be restored
// User will need to re-select files
}
},
() => ({
files: files,
})
);
}, []);

const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const selectedFiles = Array.from(e.target.files || []);
const metadata = selectedFiles.map(file => ({
name: file.name,
size: file.size,
type: file.type,
lastModified: file.lastModified,
}));
setFiles(metadata);
setFileInputs(selectedFiles);
};

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
// Upload files
await uploadFiles(fileInputs);
await clearDraft();
};

return (
<form ref={formRef} onSubmit={handleSubmit}>
<input
type="file"
multiple
onChange={handleFileChange}
/>
{files.length > 0 && (
<div>
<p>Selected files (will need to be re-selected after restore):</p>
<ul>
{files.map((file, index) => (
<li key={index}>
{file.name} ({file.size} bytes)
</li>
))}
</ul>
</div>
)}
<button type="submit">Submit</button>
</form>
);
}

Save Small Files as Base64

For small files (< 5MB), you can save them as base64:

function FileUploadWithBase64() {
const [files, setFiles] = useState<{ name: string; data: string; type: string }[]>([]);

const { formRef, restoreValues, clearDraft } = useFormAutosave(
'file-upload-base64',
{ autoRestore: false }
);

const convertToBase64 = (file: File): Promise<string> => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result as string);
reader.onerror = reject;
reader.readAsDataURL(file);
});
};

const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const selectedFiles = Array.from(e.target.files || []);
const convertedFiles = await Promise.all(
selectedFiles.map(async (file) => ({
name: file.name,
data: await convertToBase64(file),
type: file.type,
}))
);
setFiles(convertedFiles);
};

useEffect(() => {
restoreValues(
(field, value) => {
if (field === 'files' && Array.isArray(value)) {
setFiles(value);
}
},
() => ({ files })
);
}, []);

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
// Convert base64 back to files if needed
await clearDraft();
};

return (
<form ref={formRef} onSubmit={handleSubmit}>
<input type="file" multiple onChange={handleFileChange} />
{files.map((file, index) => (
<img key={index} src={file.data} alt={file.name} style={{ maxWidth: '200px' }} />
))}
<button type="submit">Submit</button>
</form>
);
}

Upload Immediately and Save URLs

Best approach for production: upload files immediately and save URLs:

function FileUploadWithURLs() {
const [fileUrls, setFileUrls] = useState<string[]>([]);
const [uploading, setUploading] = useState(false);

const { formRef, restoreValues, clearDraft } = useFormAutosave(
'file-upload-urls',
{ autoRestore: true }
);

useEffect(() => {
restoreValues(
(field, value) => {
if (field === 'fileUrls' && Array.isArray(value)) {
setFileUrls(value);
}
},
() => ({ fileUrls })
);
}, []);

const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const files = Array.from(e.target.files || []);
setUploading(true);

try {
// Upload files immediately
const urls = await Promise.all(
files.map(async (file) => {
const formData = new FormData();
formData.append('file', file);
const response = await fetch('/api/upload', {
method: 'POST',
body: formData,
});
const { url } = await response.json();
return url;
})
);

setFileUrls([...fileUrls, ...urls]);
} finally {
setUploading(false);
}
};

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
// Submit form with file URLs
await clearDraft();
};

return (
<form ref={formRef} onSubmit={handleSubmit}>
<input
type="file"
multiple
onChange={handleFileChange}
disabled={uploading}
/>
{uploading && <p>Uploading files...</p>}
{fileUrls.map((url, index) => (
<div key={index}>
<img src={url} alt={`Upload ${index}`} style={{ maxWidth: '200px' }} />
</div>
))}
<button type="submit">Submit</button>
</form>
);
}

With React Hook Form

Using React Hook Form for file uploads:

import { useForm } from 'react-hook-form';
import { useFormAutosave } from '@form-guardian/react';
import { useEffect } from 'react';

interface FormData {
title: string;
fileUrls: string[];
}

function FileUploadWithRHF() {
const { register, handleSubmit, setValue, getValues, watch } = useForm<FormData>({
defaultValues: {
title: '',
fileUrls: [],
},
});

const { formRef, restoreValues, clearDraft } = useFormAutosave(
'file-upload-rhf',
{ autoRestore: false }
);

useEffect(() => {
restoreValues(
(field, value) => {
setValue(field as keyof FormData, value, { shouldValidate: false });
},
() => getValues()
);
}, []);

const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const files = Array.from(e.target.files || []);
// Upload and get URLs
const urls = await uploadFiles(files);
setValue('fileUrls', [...getValues('fileUrls'), ...urls]);
};

const onSubmit = async (data: FormData) => {
await submitForm(data);
await clearDraft();
};

return (
<form ref={formRef} onSubmit={handleSubmit(onSubmit)}>
<input {...register('title', { required: true })} placeholder="Title" />
<input type="file" multiple onChange={handleFileChange} />
<button type="submit">Submit</button>
</form>
);
}

Best Practices

  1. Upload files immediately - Don't wait for form submission
  2. Save file URLs - Not file objects
  3. Handle upload errors - Show user-friendly error messages
  4. Show upload progress - Let users know files are uploading
  5. Validate file size - Prevent saving huge files
  6. Clear drafts after submission - Remove file references

Common Pitfalls

❌ Don't Try to Save File Objects Directly

// ❌ Wrong - File objects cannot be serialized to IndexedDB
const file = e.target.files[0];
saveDraft({ file }); // Won't work

✅ Save File URLs or Metadata

// ✅ Correct - Save URLs or metadata
const url = await uploadFile(file);
saveDraft({ fileUrl: url });

Next Steps