shakacode / re-formality

Form validation tool for reason-react
https://re-formality.now.sh
MIT License
244 stars 36 forks source link

File upload example -- <input type="file"> handling #81

Closed faseidl closed 4 years ago

faseidl commented 4 years ago

Can you provide (or point me to) an example of using re-formality to create a file upload form? I've not been able to figure out how to handle <input type="file"> fields.

alex35mil commented 4 years ago

@faseidl It should be pretty much the same way as you handle text input:

module Form = [%form
  type input = { files: array(Webapi.File.t) };
  ...
];

<input
  type_="file"
  onChange={event =>
    form.updateFiles(
      (input, files) => { files: files },
      event->ReactEvent.Form.target##files->Js.Array.from,
    )
  }
/>

P.S. I didn't run the example. Let me know if it doesn't work.

faseidl commented 4 years ago

Thanks, Alex! That was a big help! I didn't know about the Webapi module and was at a loss for how to declare the input and access the event data.

faseidl commented 4 years ago

Now, the question becomes, how does one take the File object and make a POST request that sends it to a server in ReasonML? Googling for an hour only seems to suggest that nobody has quite figured out how to do the appropriate ReasonML bindings for FormData.

alex35mil commented 4 years ago

Yeah, bs-webapi is essential for working with the DOM.

Though it doesn't have FormData bindings yet and I have it implemented internally. This example should give an idea:

module FormData = {
  type t = Fetch.formData;
  [@bs.new] external make: unit => t = "FormData";
  [@bs.send] external append: (t, string, 'a) => unit = "append";
};

let formData = FormData.make();

formData->FormData.append("file", file);

Fetch.fetchWithInit(
  url,
  Fetch.RequestInit.make(
    ~method_=Post,
    ~body=formData->Fetch.BodyInit.makeWithFormData,
    (),
  ),
);
faseidl commented 4 years ago

Ah, your approach is tighter than what I came up with:

/**
 * Hacked bindings for FormData that provide just enought functionatity to do file uploads
 * in ReasonML.
 *
 * To use, add the following script to your application's index.html file.
 *
 *  <!-- FasHacks -->
    <script>
        let FasHacks = {
            formData: {
                create: function() {
                    return new FormData();
                },

                append: function (formdata, key, value) {
                    formdata.append(key, value);
                    return formdata;
                }
            }
        }
    </script>
 */
module FormData = {
  [@bs.val] external create: unit => Js.t('a) = "FasHacks.formData.create";

  [@bs.val]
  external appendString: (Js.t('a), string, string) => Js.t('a) =
    "FasHacks.formData.append";

  [@bs.val]
  external appendInt: (Js.t('a), string, int) => Js.t('a) =
    "FasHacks.formData.append";

  [@bs.val]
  external appendFile: (Js.t('a), string, Webapi.File.t) => Js.t('a) =
    "FasHacks.formData.append";
};

Then I was able to use it with Axios:

  let createNewAsset = (file: Webapi.File.t, title: string) => {
    let formdata =
      FasHacks.FormData.(
        create()->appendFile("file", file)->appendString("title", title)
      );
    Js.Promise.(
      Axios.postData(apiPath("/media-assets"), formdata)
      |> then_(response => resolve(Model.MediaAsset.parse(response##data)))
    );
  };
alex35mil commented 4 years ago

I believe it can be closed. Feel free to re-open if you have any other questions.