Using Reason Apollo and the File API


#1

I’m building a CRUD app with reason-apollo and one feature I want to have is a mutation query that uploads a file. I’m having a difficult time using the DOM’s File object as a variable in my particular mutation render prop. It looks like this, when there is a file in state a button render that invokes the mutation on click.

let str = ReasonReact.string;

module Data = {
  type file = {
    lastModified: int,
    lastModifiedDate: string,
    name: string,
    size: int,
    type_: string,
    webkitRelativePath: string,
  };
};

type fileArray = array(Data.file);

type state = {files: fileArray};

type actions =
  | HandleFileChange(fileArray);

let component = ReasonReact.reducerComponent("NewTracks");

module SingleUpload = [%graphql
  {|
  mutation singleUpload($file: Upload!, $path: String!) {
      singleUpload(file: $file, path: $path) {
        filename
      }
  }
|}
];

module SingleUploadMutation = ReasonApollo.CreateMutation(SingleUpload);
let make = _children => {
  ...component,
  initialState: () => {files: [||]},
  reducer: (action, _state) =>
    switch (action) {
    | HandleFileChange(fileArray) => ReasonReact.Update({files: fileArray})
    },
  render: ({state, send}) =>
    <div>
      <button
        className=Styles.basicButton
        onClick=(_event => ReasonReact.Router.push("/"))>
        (str("Home"))
      </button>
      <div className=Styles.segment>
        <p> ("Upload a new track here" |> str) </p>
        <input
          id="track"
          type_="file"
          onChange=(
            event => {
              let files = ReactEvent.Form.target(event)##files;
              send(HandleFileChange(files));
            }
          )
        />
      </div>
      <SingleUploadMutation>
        ...(
             (mutate, _) =>
               switch (Array.length(state.files)) {
               | 0 => <div> ("Put a file!" |> str) </div>;
               | _ =>
                 let selectedFile = Belt.Array.getExn(state.files, 0);

                 let singleUploadMutation =
                   SingleUpload.make(~file=selectedFile, ~path="/tracks", ());

                 <button
                   className=Styles.basicButton
                   onClick=(
                     _event =>
                       mutate(~variables=singleUploadMutation##variables, ())
                       |> ignore
                   )>
                   ("Upload!" |> str)
                 </button>;
               }
           )
      </SingleUploadMutation>
    </div>,
};

With this current code, SelectedFile has type Data.file but is expected to be Js.Json.t. I’ve tried encoding as Json with this module following this thread on Working with results from DOM API, but no luck.

module Encode = {
  let toJson = file =>
    Json.Encode.(
      object_([
        ("lastModified", file##lastModified |> int),
        ("lastModifiedDate", file##lastModifiedDate |> string),
        ("name", file##name |> string),
        ("size", file##size |> int),
        ("type", file##type_ |> string),
        ("webkitRelativePath", file##webkitRelativePath |> string),
      ])
    );
};

A good first step to figuring this out might be, has anyone had much luck working with the File API with Reason in general?


#2

I’ve filed an issue (https://github.com/apollographql/reason-apollo/issues/151) suggesting that an example would be very helpful, since this does seem to be rather difficult. In the mean time, I’m employing raw JavaScript to invoke mutate directly: https://gist.github.com/nlfiedler/0da484d9009ea190dc039c3c10d4a390