Coder Social home page Coder Social logo

Comments (16)

AlemTuzlak avatar AlemTuzlak commented on May 17, 2024 1

@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.

AlemTuzlak avatar AlemTuzlak commented on May 17, 2024 1

@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.

cbude avatar cbude commented on May 17, 2024 1

@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.

cbude avatar cbude commented on May 17, 2024 1

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.

alexwhb avatar alexwhb commented on May 17, 2024

@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.

aknegtel avatar aknegtel commented on May 17, 2024

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.

alexwhb avatar alexwhb commented on May 17, 2024

@AlemTuzlak Awesome! Thanks for that resource. I'll give it a try.

from remix-hook-form.

cluk3 avatar cluk3 commented on May 17, 2024

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 of parseFormData and validateFormData as @cbude suggested since getValidatedFormData is already calling parseFormData 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 from Request to Request | FormData (same as parseFormData)?

from remix-hook-form.

chiptus avatar chiptus commented on May 17, 2024

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.

chiptus avatar chiptus commented on May 17, 2024

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

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.

cluk3 avatar cluk3 commented on May 17, 2024

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.

chiptus avatar chiptus commented on May 17, 2024

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.

cluk3 avatar cluk3 commented on May 17, 2024

I don't think so cause the library is using useSubmit under the hood and this is how useSubmit works

from remix-hook-form.

chiptus avatar chiptus commented on May 17, 2024

do you mean useSubmit changes the encType? so why have the encType prop on the form component anyway?

from remix-hook-form.

cluk3 avatar cluk3 commented on May 17, 2024

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.

chiptus avatar chiptus commented on May 17, 2024

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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.