seasonedcc / remix-forms

The full-stack form library for Remix and React Router
https://remix-forms.seasoned.cc
MIT License
492 stars 25 forks source link

Support `<input type="file" />` validation #101

Open mdoury opened 1 year ago

mdoury commented 1 year ago

I'm trying to include a file field for image uploading using the unstable_createFileUploadHandler api and would like to include some server side validation in addition to the client validation.

In Issue #8 @danielweinmann said file input validation is unnecessary but in my opinion this is not always true. I'm building an image processing tool relying on multipart form data and I'd like to ensure that a file is actually submitted by the user, but I'd also like to restrict the type and the size of the files a user can upload. It would be really handy if file input validation could be done in the same workflow and with the same efficiency as the other input fields, i.e. progressive enhancement, E2E type safety, accessibility, etc.

I really like remix-forms and its apis but this feature is a deal breaker for me, so I really hope you reconsider supporting file upload in the future 🙏

diogob commented 1 year ago

@mdoury do you have any suggestions on how that file validation API would look like? Examples of pseudo-code would greatly help us to design a file uploading feature.

mdoury commented 1 year ago

@diogob I was thinking of something like this :

import type { ActionArgs } from "@remix-run/node";
import { NodeOnDiskFile } from "@remix-run/node";
import { z } from "zod";
import { file } from "remix-forms";

import { Form } from "~/form";
import { formAction } from "~/form-action";
import { processFile as mutation } from "~/mutations";
import { fileUploadHandler as uploadHandler } from "~/upload-handlers";

const schema = z.object({
  image: file({
    schema: z.instanceof(NodeOnDiskFile),
    accept: ["image/jpeg", "image/jpg", "image/png", "image/webp"] as const,
    maxSize: 5_000_000,
  }),
});

export async function action({ request }: ActionArgs) {
  return formAction({
    request,
    schema,
    mutation,
    successPath: "/success",
    uploadHandler,
  });
}

export default () => <Form schema={schema} encType="multipart/form-data" />;

Users would provide the uploadHandler, this way it can be customized and/or composed. The file helper would allow users to specify the schema expected from the uploadHandler, the file type corresponding to the accept attribute, as well as the maximum allowed file size.

remix-form would handle FormData parsing using unstable_parseMultipartFormData. It would also generate an <input type="file" /> field with the appropriate attributes, a11y attributes, and basic error handling, as well as the client side file size check.

Note that the encType prop on the Form component may not be necessary as it may be possible to infer that the schema contains one or more fields configured using the file helper.

What do you think?

diogob commented 1 year ago

@mdoury I like this idea. I'll play around with the code and gather a bit more info on the implementation details and post my findings here.

mdoury commented 1 year ago

@diogob Cool, let me know if I can help.

ben-smartbear commented 1 year ago

Any update to this?

founderblocks-sils commented 1 year ago

Hey, I found this as a good source of inspiration, using that making a decently working custom field for files, using the watch function:

https://codesandbox.io/s/react-hook-form-uploadfile-with-thumbnail-preview--drag-and-drop-p11gy?file=/src/App.js

That paired with the code at https://github.com/seasonedcc/remix-forms/issues/8 should make it possible to work with remix-forms and files. Builtin support would be really splendid though, maybe this helps with that too :)

janhesters commented 1 year ago

Would be great, if this was supported!

Screenshot 2023-04-08 at 10 48 53

Also, would be awesome if this is designed in a way to handle "drag-and-drop" as well as regular uploads. (That you can somehow decide which input to render.)