Comments (16)
@alexwhb Hey Alex, thank you for the kind words! Before I answer this, what happens with the images after you upload them?
from remix-hook-form.
@alexwhb Sorry for the slow reply! So I wrote an article a while back where I upload images directly to supabase (which I think uploads it to an S3 bucket) so you could do it like this:
https://alemtuzlak.hashnode.dev/uploading-images-to-supabase-with-remix
What I usually would recommend with file uploads is to have validation where its:
z.instanceOf(File).or(z.string())
and when you upload it you parse it and return the url on the server so you can actually save the url to wherever and pass the validation.
When it comes to your problem @aknegtel did you try using the text decoder (you can refer to the same link I provided above) for parsing the rest of the values?
unstable_parseMultipartFormData
relies on you parsing everything and giving it back to formData, so you need to parse the non file values as well and return them as text.
from remix-hook-form.
@alexwhb Sorry for the slow reply! So I wrote an article a while back where I upload images directly to supabase (which I think uploads it to an S3 bucket) so you could do it like this: https://alemtuzlak.hashnode.dev/uploading-images-to-supabase-with-remix What I usually would recommend with file uploads is to have validation where its:
z.instanceOf(File).or(z.string())
and when you upload it you parse it and return the url on the server so you can actually save the url to wherever and pass the validation.When it comes to your problem @aknegtel did you try using the text decoder (you can refer to the same link I provided above) for parsing the rest of the values?
unstable_parseMultipartFormData
relies on you parsing everything and giving it back to formData, so you need to parse the non file values as well and return them as text.
I wrote mine like this which works quite well 😄
image: zod.instanceof(File).refine((file) => {
return file && file.size <= MAX_FILE_SIZE
}, `Max image size is 5MB.`)
.refine(
(file) => file && ACCEPTED_IMAGE_TYPES.includes(file.type),
"Only .jpg, .jpeg, .png formats are supported."
).or(zod.string().url()).optional(),
from remix-hook-form.
Hi, I'm also having trouble with file upload mixed with other fields.
The setup you use in the documentation for "File upload" doesn't seem to work. I've encountered your problem @alexwhb, where the action says it could not parse as FormData. I resolved it but I don't remember how exactly (I'll try to remember).
My problem resides in the parsing of values not working correctly when mixing files and other content. The
validateFormData
function returns errors because my resolver expects an array and it encounters a string, as you can see here:Entering action function: Files: [{}] { files: { message: 'Expected array, received string', type: 'invalid_type', ref: undefined }, images: { message: 'Expected array, received string', type: 'invalid_type', ref: undefined }, published: { message: 'Expected boolean, received string', type: 'invalid_type', ref: undefined } }My action (down below) function is almost exactly like the example, however, I've tried countless variations. I've tried with stringifyAllValues true and false. I've even tried using a custom
generateFormData
.const formData = await unstable_parseMultipartFormData( request, unstable_createMemoryUploadHandler(), ) // The file will be there console.log("Files: ", formData.get("files")) // validate the form data const { errors, data: product } = await validateFormData<Product>( formData, resolver, ) if (errors) { console.log(errors) return json({ errors, product }) }
Hi!
I had similar issued until I used parseFormData
before calling validateFormData
. That seems to do the trick with getting everything parsed correctly 😄
from remix-hook-form.
@AlemTuzlak Absolutely. This library is a huge help, and you also introduced me to Zod, which I've subsequently used on another project too.
I'll answer that in two ways, because I'm not 100% sure what way you mean.
1.) I'm currently uploading the file just to my local file system, but eventually I'll likely use S3. I've tested using S3 with S3rver
locally using the image buffer and that also works when I'm not using Remix-Hook-Form.
2.) So when I attempt to upload an image this way either I get just a string with the image name in my request data in the action, or I get nothing in the action depending on how I configure the form. If I attempt to use the standard Remix multipart file upload method I get an error: TypeError: Could not parse content as FormData.
here's what my action code looks like in that case:
export const action: ActionFunction = async ({request}) => {
const directory = "/public/images"
const uploadHandler = unstable_composeUploadHandlers(
unstable_createFileUploadHandler({
directory,
maxPartSize: 5_000_000,
file: ({filename}) => {
const newFileName = `${uuidv4()}.${filename.split('.').pop()}`
prisma.image.create({
data: {
url: `/images/${newFileName}`
}
}).then(res => console.log(res))
return newFileName
},
}),
// parse everything else into memory
);
const formData = await unstable_parseMultipartFormData(
request,
uploadHandler
);
console.log(formData) // this never gets hit.
return {};
Let me know if you want any additional info. Happy to to provide it. And thank you for taking a look. Much appreciated.
from remix-hook-form.
Hi, I'm also having trouble with file upload mixed with other fields.
The setup you use in the documentation for "File upload" doesn't seem to work. I've encountered your problem @alexwhb, where the action says it could not parse as FormData. I resolved it but I don't remember how exactly (I'll try to remember).
My problem resides in the parsing of values not working correctly when mixing files and other content. The validateFormData
function returns errors because my resolver expects an array and it encounters a string, as you can see here:
Entering action function:
Files: [{}]
{
files: {
message: 'Expected array, received string',
type: 'invalid_type',
ref: undefined
},
images: {
message: 'Expected array, received string',
type: 'invalid_type',
ref: undefined
},
published: {
message: 'Expected boolean, received string',
type: 'invalid_type',
ref: undefined
}
}
My action (down below) function is almost exactly like the example, however, I've tried countless variations. I've tried with stringifyAllValues true and false. I've even tried using a custom generateFormData
.
const formData = await unstable_parseMultipartFormData(
request,
unstable_createMemoryUploadHandler(),
)
// The file will be there
console.log("Files: ", formData.get("files"))
// validate the form data
const { errors, data: product } = await validateFormData<Product>(
formData,
resolver,
)
if (errors) {
console.log(errors)
return json({ errors, product })
}
from remix-hook-form.
@AlemTuzlak Awesome! Thanks for that resource. I'll give it a try.
from remix-hook-form.
I also was able to get it working by following the various useful tips in the comments, this is the code in case anybody needs it for reference
async function decodeTextFields({ filename, data }) {
if (!filename) {
const chunks = [];
for await (const chunk of data) {
chunks.push(chunk);
}
const buffer = Buffer.concat(chunks);
const textDecoder = new TextDecoder();
return textDecoder.decode(buffer);
}
// if it has a filename it's the file we want to upload, by returning undefined we delegate the processing of the file to
// the next uploadHandler, in this example the s3UploadHandler
return undefined;
};
export async function action({ request }: ActionFunctionArgs) {
const uploadHandler: UploadHandler = composeUploadHandlers(
decodeTextFields,
s3UploadHandler,
);
const formData = await parseMultipartFormData(request, uploadHandler);
const avatarUrl = formData.get("avatar");
const {
errors,
data,
receivedValues: defaultValues,
} = await getValidatedFormData<CreateEventSchema>(
formData as unknown as Request,
resolver,
);
if (errors) {
return json({ errors, defaultValues }, { status: 422 });
}
// do whatever you need to do with the data here, send it to the db I guess :)
}
@AlemTuzlak I have a couple of proposals:
- I would strongly advise updating the docs with a working example like this one so that whoever will have the same problem in the future will find the solution easily. I guess after updating the docs we could close the issue?
- I'm using
getValidatedFormData
directly instead ofparseFormData
andvalidateFormData
as @cbude suggested sincegetValidatedFormData
is already callingparseFormData
internally, but Typescript was screaming at me cause I'm passing FormData instead of Request, when it actually works with a FormData as well. Do you think it would be correct to change the type fromRequest
toRequest | FormData
(same asparseFormData
)?
from remix-hook-form.
while doing all of this, I still get "Could not parse content as FormData.".
I added some console.log inside remix node_modules/@remix-run/server-runtime/dist/formData.js
and I see that content type is application/x-www-form-urlencoded
and not multipart/form-data
as required. @AlemTuzlak do you change it somewhere?
my Form encType is multipart/form-data
from remix-hook-form.
while doing all of this, I still get "Could not parse content as FormData.". I added some console.log inside remix
node_modules/@remix-run/server-runtime/dist/formData.js
and I see that content type isapplication/x-www-form-urlencoded
and notmultipart/form-data
as required. @AlemTuzlak do you change it somewhere?my Form encType is
multipart/form-data
adding
submitConfig: {
encType: "multipart/form-data",
},
to my useRemixForm
fixed that issue, although I would expect the encType from the Form element to be used.
from remix-hook-form.
That's s good point, I forgot to mention it in my example. @AlemTuzlak sorry for pinging you again, but it would be nice to update the docs, I can open a PR but first I want to be sure you'll be up to review and eventually merge it
from remix-hook-form.
That's s good point, I forgot to mention it in my example. @AlemTuzlak sorry for pinging you again, but it would be nice to update the docs, I can open a PR but first I want to be sure you'll be up to review and eventually merge it
I think this is something to fix in the library and not something to document
from remix-hook-form.
I don't think so cause the library is using useSubmit under the hood and this is how useSubmit works
from remix-hook-form.
do you mean useSubmit
changes the encType? so why have the encType prop on the form component anyway?
from remix-hook-form.
You don't need it on the form, when using useSubmit you are managing the form submission through the submit api and not through the Form component. You can check how it works on Remix documentation
from remix-hook-form.
You don't need it on the form, when using useSubmit you are managing the form submission through the submit api and not through the Form component. You can check how it works on Remix documentation
remix docs says to put it on the form. I don't use useSubmit
directly
from remix-hook-form.
Related Issues (20)
- Number validation fails HOT 3
- submitData string issue HOT 5
- [suggestion] Implicit Inference of Form Schema in `getValidatedFormData` and `validateFormData` HOT 7
- `formState.isSubmitting` is `true` when the loaders are refetching HOT 8
- Property 'root' does not exist on type HOT 7
- Form State isDirty not resetting after submit HOT 1
- Could not parse content as FormData HOT 1
- Remix Hook Form File Upload HOT 1
- 'mode' does not exist in type 'UseRemixFormOptions' HOT 3
- getValidatedFormData auto parse JSON cause error if value is JSON string HOT 3
- reset method does not accept options
- TypeScript: Return type of useRemixForm hook is incorrect. HOT 6
- Have to set stringifyAllValues: false else all strings are double quoted HOT 2
- (bug): Remix hook form is not behaving as expected or documented HOT 5
- Form Reset On Success HOT 1
- remix-hook-form not compatible with 7.51.0 of react-hook-form
- When using Link to navigate to other pages, isSubmitting is also true, which is not as expected.
- Allow `encType: "application/json"` from `submitConfig`
- Multiple forms on a single page HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from remix-hook-form.