Image upload in react? [SOLVED]

Hi all,

[Please see my latest post below as I’ve given up on CFS now]

I’m basically trying to have the profile image reactively update when the new one is uploaded. I did this before in Blaze like so:

{{#with avatarImage}}
<a href="#" data-toggle="modal" data-target="#newAvatar"><img class="avatar" src="{{this.url}}" />
{{/with}}

Still getting a bit confused with some aspects of React, so this should help me understand stuff better for my other projects…

Cheers in advance!

In React, the whole component re-renders at a time. In Blaze, with could help you isolate reactivity to a small part of your template, but you don’t need to do that anymore.

1 Like

Hi sashko, thanks for your reply.

I ‘kind of’ get that! However, it’s not reactive right now. It’s uploading the image but not reactively changed it once it has been uploaded.

Right now, my code is like this:

avatarSubmit(event) {
 event.preventDefault();
 FS.Utility.eachFile(event, function(file) {
  Avatars.insert(file, function (err, fileObj) {
   if (err){
   // error
   toastr.error(error.reason);
   } else {
   // Success!
   toastr.success('Upload successful');
   Meteor.users.update({_id: Meteor.userId() }, {$set: { 'profile.avatar': fileObj._id }});
   };
  });
 });
},

render() {
let avatar;
let { currentUser } = this.data;

avatar = (
  <img src={ currentUser.profile.avatar }></img>
)

and then renders with:

{ avatar }

I’m sure it’s just something I’m missing though…

Thanks again.

Tom.

Try something like this:

Example = React.createClass({
  mixins: [ReactMeteorData],
  getMeteorData() {
      return {
          currentUser: Meteor.user()
      };
  },

  avatarSubmit(event) {
    // something something
    Meteor.users.update({_id: Meteor.userId() }, {$set: { 'profile.avatar': fileObj._id }});
  },

  render: function() {
    return (
      <div>
        <img src={this.data.currentUser.profile.avatar} />
      </div>
    );
  }

});

If I’m not mistaken that mixin ReactMeteorData is going to detect change in underlying data, and call render() again, thus updating your avatar.

4 Likes

Hi sergio, thanks for your response.

I didn’t include the ReactMeteorData mixin as I thought that was a given! My bad :frowning:

Reactivity seems to be working now, but the images aren’t. To make it reactive before, I had to make a reference to the fileObj._id rather than the URL of the new file (otherwise, it would try change the avatar before the upload had finished, causing the image to break).

Before, in Blaze, there was a reference to {{this.url}} which I think is what is lacking here as I’m not getting the url of the file, only the reference to it currently. For some reason I can’t work it out in my head so I need some guidance :frowning:

Thanks again for your reply!

Tom.

I’m still struggling with this and I’m taking a different approach unless someone can offer another solution!

It seems the {{this.url}} method CollectionFS uses a bit out of my scope to replicate in react, so instead I’m looking at only updating the avatar url of the user profile after the image has been uploaded. I’ve tried a few methods, but could do with some pointers if anyone has used CollectionFS before (I have done a Google search and had not much joy so far).

Here is my new avatarSubmit method:

avatarSubmit(event) {
 event.preventDefault();
 FS.Utility.eachFile(event, function(file) {
  Avatars.insert(file, function (err, fileObj) {
   if (err){
    // error
    toastr.error(error.reason);
   } else {
    // Success!
    toastr.success('Upload successful');
    Tracker.autorun(function() {
     var avatarURL = { 'profile.avatar': '/cfs/files/avatars/' + fileObj._id };
     if (fileObj.isUploaded()) {
      Meteor.users.update({_id: Meteor.userId() }, {$set: avatarURL});
     }
    });
   }
  });
 });
},

I tried simply doing if (fileObj.isUploaded()) without tracker but it made no difference.

Cheers,

Tom.

I’m guessing no-one has a solution to this? If so, does anyone have any examples I can look at for image uploads in React and Meteor? I’m more than happy to look at alternatives but this is really starting to make my hair go grey :sweat:

Cheers,

Tom.

These are early days for React+Meteor @korus90, it might be harder to get help in this area to start.

Yes I thought as much, but with users such as @SkinnyGeek1010 that have used React quite a lot (at least to my knowledge!) I was hoping someone could point me in the right direction.

I do have a semi-workable solution by using setTimeout on the update link to the image, but this is far from ideal and I was told it wasn’t a good idea (justifiably so).

I will explore more options and if I find a solution I’ll post it here. I’m sure someone else will find this thread useful at some point!

1 Like

@korus90, I think latency compensation is messing it up for you. I’d suggest you try @sergiotapia’s approach but with a remote method (Meteor method, without stub), and update the URL on the server side. The change would then propagate to client-side via Meteor.user() only after the URL is saved. Do make sure the image resource is available at the said URL before updating the user document.

1 Like

Hi gaurav,

That sounds like it may work. Not thought of trying to move the update it a method, but it’s certainly worth a go!

I’ll be back shortly with results. Might have to be when I get home now though (nearly 5pm here).

Perhaps i’m not understanding the full requirements but it seems like you’re going about it the hard way. It seems like you’re trying to do thing imperatively while React wants to be declarative. I think that’s root of the issue.

There is also two separate problems to solve:

  • 1 upload and image and save url to the DB
  • 2 update the UI when the profile data changes

For a min. let’s assume that the user can click a button and upload a photo… from this app or another. Ultimately the user.profile.avatar field will have a URL that changes when a new photo is uploaded.

If the user profile just has a user object like this on the client:

{profile: {avatarUrl: "foo.com/bar.png"}}

then getting the UI to change the photo on change won’t be any different than updating the user’s name. If the UI re-renders we simply want the URL to be updated and the browser will re-draw the photo:

React.createClass({
  mixins: [ReactMeteorData],

  getMeteorData() {
      return {
        viewer: Meteor.user()
      };
  },

  render() {
    return (
      <div>
        Username: {viewer.profile.userName}
        <img src={viewer.profile.avatarUrl} />
      </div>
    );
  }
});

Make sure this bit is working before even worrying about the upload. You can use the Meteor shell to update the user avatar url to make sure it’s working. React will re-draw the photo.


Right, so now that is working the second bit is not even related to React… as you see above even the Meteor shell can allow React to update the avatar. There are several ways to upload photos and i’ve only used S3 to host them. Any other ways doesn’t make sense for most use cases.

First try to find a solution on Npm or Github and then fallback to atmosphere if needed. You can upload directly to S3 from the client, upload with the client but sign on the server, or upload the image to your server and your server uploads to S3. Unless there is some kind of security issue, just do it from the client directly.

In all cases you will get a URL on success and that can be saved to the user profile the normal way (clientside or serverside), you’re just automating the process you did with the Meteor shell.

In the past i’ve rolled my own micro lib to upload to S3 (using POST requests), but I would recommend using a library.

These look promising:
https://www.npmjs.com/package/s3-browser-direct-upload
https://www.npmjs.com/search?q=s3+upload

Then this code can live outside of the React/View layer:

// client/domains/user.js

UserDomain = {
  uploadUserAvatar(userId, file) {
     _uploadToS3(file, function(err, url){
        Meteor.users.update(userId, {$set: 'profile.avatarUrl': url });
     })
   },

  _uploadToS3(file, callback) {
   ....
  },
}
4 Likes

An epic, insightful and (more importantly) helpful reply as always @SkinnyGeek1010!

I decided not to look at this when I got home last night as I was a bit too tired, so I’ll take a look in the next few hours and report back.

Cheers,

Korus.

1 Like

Hi everyone,

Thank you all for your help with this. This is now solved!

I ended up doing what @SkinnyGeek1010 suggested and started using Amazon S3 for storage and used edgee:slingshot since it gets rave reviews everywhere. I can see why to be honest.

Here is what I ended up with for my avatarSubmit component:

avatarSubmit(event) {
event.preventDefault();

// Get files
files = event.target.files[0];

// Set uploader
const uploader = new Slingshot.Upload( "uploadToAmazonS3" );

// Upload files
uploader.send(files, function( err, downloadUrl) {
  if (err) {
    toastr.error(err.reason);
  }
  else {
    toastr.success("Success: Avatar uploaded");
    avatarUrl = { 'profile.avatar': downloadUrl };
    Meteor.users.update({ _id: Meteor.userId() }, { $set: avatarUrl });
  }
});

},

Since the process can’t complete without getting the callback URL from S3, I don’t have the issues of Meteor wanting to load the images before they’re uploaded anymore. In fact, without going down the npm route I think slingshot seems to be the way to go.

Is it just me or is the ‘code’ function of the forums not working right by the way?

2 Likes

Awesome, glad it helped! :thumbsup:

Is it just me or is the ‘code’ function of the forums not working right by the way?

sometimes i have to take away the last backticks and retype them for it to work… or it’ll act goofy.

Maybe anyone has recommendations what to use with meteor 1.3 and react for file uploading? I used “tomitrescak/meteor-uploads” before, but it seems as not really good choice now.

What about Slingshot, that I used above? Not tried it in 1.3 but don’t really see how it should change the way it interacts with Amazon S3.

Slingshot for S3, but I looked for a local server storage. But okay, I gave a try for S3 and what I see. Yes, it’s easy to upload and manage images, but there is no really good way to resize pictures before upload. I spent a lot of time on this question and it gets me angry.

For S3 uploading I used edgee:slingshot and lepozepo:s3 and the last one on my opinion preferable, because it could handle array of images from the box. I had that vanila dreams where I could resize pictures on client side and all images questions will pass by my server directly to S3 and I gave a shoot for thinksoftware:image-resize-client and when I converted 2000x4000 image to 300px thumb and have seen the result … Okay, there is no way to use this in production. It’s terrible. Then I tried to go back to a imagemagick with classcraft:imagemagick and this package didn’t worked at all. Now I’m frustrated. Is there any way to simple manage uploads and make thumbnails? Some simple solution which could process a bunch of images from onDrop react event?

I think you may want to broaden the techniques/tools to NPM/Javascript and use one of those instead of Meteor packages. You may even want to spin up a separate server for just imagemagick to run (so it doesn’t bring down your server under load)… which would eliminate the Atmosphere lock in and would free up CPU cycles.

However, the easiest thing to do would be to use a service like Filepicker (now filestack i guess) that allows you to choose a file, resize/crop on the client, and send to S3:

https://www.filestack.com/home
https://www.filestack.com/docs/image-transformations/resize
https://www.filestack.com/docs/image-transformations/crop

Thanks,

Currently I’ve built a chain from React-dropzonePica image resizerLepozepo:s3.

Pica is nice solution, but it tooks a time to handle it, but the result is perfect. Much better than a standard canvas resize and it’s happening on a client side, as I wanted.

I think you may want to broaden the techniques/tools to NPM/Javascript and use one of those instead of Meteor packages.

I tried few npm packages for upload and resize, but they didn’t work from the start and I gave up on them.

2 Likes