How would you do file uploads in a React-Relay app?

Nick Retallack picture Nick Retallack · Oct 11, 2015 · Viewed 8.6k times · Source

A file upload seems like a mutation. It's often accompanied by other data. But it's a big binary blob, so I'm not sure how GraphQL can deal with it. How would you integrate file uploads into an app built with Relay?

Answer

Fan Jin picture Fan Jin · Feb 23, 2016

First you need to write the Relay update in your frontend component. Like this:

onDrop: function(files) {
  files.forEach((file)=> {
    Relay.Store.commitUpdate(
      new AddImageMutation({
        file,
        images: this.props.User,
      }),
      {onSuccess, onFailure}
    );
  });
},

And then follow by implementing the mutation on the frontend:

class AddImageMutation extends Relay.Mutation {
   static fragments = {
     images: () => Relay.QL`
       fragment on User {
         id,
       }`,
     };

   getMutation() {
     return Relay.QL`mutation{ introduceImage }`;
   }

   getFiles() {
     return {
       file: this.props.file,
     };
   }

   getVariables() {
     return {
       imageName: this.props.file.name,
     };
   }

   getFatQuery() {
     return Relay.QL`
       fragment on IntroduceImagePayload {
         User {
           images(first: 30) {
             edges {
               node {
                 id,
               }
             }
           }
         },
         newImageEdge,
       }
     `;
   }

   getConfigs() {
     return [{
       type: 'RANGE_ADD',
       parentName: 'User',
       parentID: this.props.images.id,
       connectionName: 'images',
       edgeName: 'newImageEdge',
       rangeBehaviors: {
         '': 'prepend',
       },
     }];
   }
 }

And last, implement the handler on the server/schema.

const imageMutation = Relay.mutationWithClientMutationId({
  name: 'IntroduceImage',
  inputFields: {
    imageName: {
      type: new GraphQL.GraphQLNonNull(GraphQL.GraphQLString),
    },
  },
  outputFields: {
    newImageEdge: {
      type: ImageEdge,
      resolve: (payload, args, options) => {
        const file = options.rootValue.request.file;
        //write the image to you disk
        return uploadFile(file.buffer, filePath, filename)
        .then(() => {
          /* Find the offset for new edge*/
          return Promise.all(
            [(new myImages()).getAll(),
              (new myImages()).getById(payload.insertId)])
          .spread((allImages, newImage) => {
            const newImageStr = JSON.stringify(newImage);
            /* If edge is in list return index */
            const offset = allImages.reduce((pre, ele, idx) => {
              if (JSON.stringify(ele) === newImageStr) {
                return idx;
              }
              return pre;
            }, -1);

            return {
              cursor: offset !== -1 ? Relay.offsetToCursor(offset) : null,
              node: newImage,
            };
          });
        });
      },
    },
    User: {
      type: UserType,
      resolve: () => (new myImages()).getAll(),
    },
  },
  mutateAndGetPayload: (input) => {
    //break the names to array.
    let imageName = input.imageName.substring(0, input.imageName.lastIndexOf('.'));
    const mimeType = input.imageName.substring(input.imageName.lastIndexOf('.'));
    //wirte the image to database
    return (new myImages())
    .add(imageName)
    .then(id => {
    //prepare to wirte disk
      return {
        insertId: id,
        imgNmae: imageName,
      };
    });
  },
});

All the code above you can find them in my repo https://github.com/bfwg/relay-gallery There is also a live demo https://fanjin.io