Rails & React: Passing an Image Between Them
Tags: codenewbie, rails, react
Published: 2020-07-24 05:26:24

So you have an app. What would make the app better? I know, let's have the user be able to upload an image to her account! Every app out there does this, so it must be easy, right? 

These were my thoughts for my application I was building. To be clear, at least for me, it was not easy. But hopefully, with a little help from this post and other helpful angels that helped me get to this point(more on this below), you too can upload an image to your application. I will note that this was just the solution that I was able to use for my specific application, where your application may benefit more from a different approach. But if you are using a React frontend and a Rails backend, read on for at least one of many solutions you can use! 

Let's start by diving into the frontend. First we need to get the picture into our application, which I did by using a form. How do you allow an image to be uploaded via our form? After looking around for a bit, I settled on using the ImageUploader package ([documentation here](https://www.npmjs.com/package/react-images-upload)) but there are many other options to choose from if the layout of ImageUploader isn't what you are looking for. Once ImageUploader was installed and imported into the file my form is on, all I had to add to the form was the following: 

```javascript
  <Form.Group >
  <ImageUploader
    withIcon={true}
    buttonText='Choose images'
    onChange={event => this.onDrop(event)}
    imgExtension={['.jpg', '.png', '.jpeg', '.gif']}
  />
  </Form.Group>
```

Seen on the page, the form will look like this:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/e2yroak6ksapgyr2a5en.png)

A few things to notice. Form.Group comes from react-bootstrap. If you are not using react-bootstrap, it's easy to add in the same chunk of code as a new element in your form in whatever way you decide to divide up your form options. ImageUploader comes with its own icon to display. If you don't like the icon it provides, you can turn withIcon to false. The buttonText can be changed to display whatever text you wish and you can also change the imgExtension array to limit image formats you may not wish to accept. 

Finally, there is a mandatory onChange that ImageUploader will need, in this case an onDrop. The corresponding function will look like this: 

```javascript
    onDrop = picture => {
        this.setState({ image: picture[0] })
    }
```

I am using state to store the form properties, which setState works great for. I am only allowing the user to upload one image at a time, so simply calling picture[0] was sufficient for my needs. There are ways to upload multiple images, but I will not be delving into that in this post. 

Now that we have the image in state, let's send that to our backend. Almost halfway there already! 

I used a dedicated api file to store all of my api calls to my Rails application, but however you make your calls, you will need to implement something called FormData in order to correctly send the image data from your React app to your api. What is FormData, you ask? I would ask good ol' Google to make sure you fully understand what it does, but for the purposes of this post I will just state that we will need to use it. 

An important thing to note about FormData in a fetch request is that it does *not* need "Content-Type" or Accept headers. In fact, if you include them in part of your headers, the FormData (and therefore the image) we are trying to send will not work as intended. If you're like me and used a headers helper method in your api file, like this: 

```javascript
const headers = () => {
    return {
        "Content-Type":"application/json",
        Accept: "application/json",
        Authorization: token()
    }
}
```
be sure to manually input your headers into the fetch request that will be sending FormData. In my case, it looked like this, as I still needed the Authorization to be send over: 

```javascript
const createBoard = (boardObject) => {
  let nestedObject = {'board': boardObject}
  let formData = objectToFormData(nestedObject)

  return fetch(`${API_ROOT}/boards/`, {
    method: "POST",
    headers: {Authorization: token()},
    body: formData
  }).then(res => res.json());
}
```
Creating a specifically formatted FormData can be a little tricky, so I employed the help of object-to-formdata([documentation here](https://www.npmjs.com/package/object-to-formdata)). Once you import object-to-formdata into your file, simply pass your desired object into an objectToFormData function and, wa-la!, you have FormData that can easily be send to your api. 

Instead of reinventing the wheel, for the bulk of the Rails work, I will pass you into the very capable hands of [this post](https://medium.com/better-programming/how-to-upload-images-to-a-rails-api-and-get-them-back-again-b7b3e1106a13) that helped me out of my own dark times of uploading images. A big thank you to the author. Check it out from part 2 onward and give him a like and a follow. He makes great stuff. Before you go, I have a neat trick to show you once you are done reading his article. If you get to the custom serializer and think, "Is there another way to do that?", head on back here for my final say. 

---

Yay! Now you have a ActiveRecord up and running and a new Cloudinary account. You'll recall that in your model that receives an image, you put this bit of code: 

```ruby
  def get_image_url    
    url_for(self.image)  
  end
```

If you want an easy way to grab that info in your serializer, throw this into the serializer that will be sending the image back to your React app: 

```ruby
  link :custom_url do |object|
    "#{object.get_image_url}"
  end 
```
You'll now have a new element in your JSON object that gives you your link! 

Hopefully now all of your image uploading dreams have come true. Have any suggestions on further optimization? Do you have a better way of doing this all together? I would love to hear them! You can help me continue to learn along with anyone else who drops by in the future. 

Thanks for the read and happy hacking!