React File Uploads to Rails

Ignas Butautas
The Startup
Published in
4 min readNov 17, 2020

Giving the user the ability to upload files to your application should be a tool all developers should have in their back pocket. Though it may seem easy in theory, there’s a lot of overhead that needs to be done to get your database and frontend setup for this type of information.

To start off, this is a guide for Rails as an API in the backend, and React.js in the frontend. First things first, when setting up your backend you’ll need to set up Active Storage so that your database can handle file uploads. It’s as simple as running:

rails active_storage:install

in your terminal, after that all you need to do is:

rails db:migrate

Note: When modeling your databases you do not need to give that model any kind of attribute. Aka:

class CreateUsers < ActiveRecord::Migration[6.0]def changecreate_table :users do |t|t.string :usernamet.string :image \\ THIS IS NOT NEEDEDendendend

Another Note: I highly recommend using a serializer gem. Before you migrate your database models. They give you a lot of power on what kind of information you send to the frontend.

Rails will do all of the heavy lifting for you to setup your backend to accept file uploads. In the background Rails is creating a polymorphic join table that will relate the information that is associated with the file. If you’d like to take a deeper dive I’d highly recommend checking out this blog post. The next step that you need to take is letting rails know where and what is being associated with your file. Guess what, it’s also super easy.

class User < ApplicationRecordhas_one_attached :imageend

All you need to do is write ‘has_one_attached:’ and have it point to essentially to any word you want. It can be :profile_image, :avatar, etc…

This essentially creates the attribute of the image. Also, if you are planning on uploading sound files, don’t shy away, all of the steps taken are essentially the same, in this case it would look something like this:

class Track < ApplicationRecordhas_one_attached :trackend

Going forward, your database is pretty set up to take in file uploads from users, but there’s one more step that you need to take and I’ll touch on that later. On your frontend you will find a few headaches here, there are many different ways that you can send files to the backend, and I’m only going to show the easiest way (in my opinion.)

To start off, you will need to install ‘axios’ (which is as simple as ‘npm install axios — save’ in your terminal) and then on the top of your React file you’ll want to import axios like so:

import axios from 'axios'

Let’s say you have a User signup page where a user has to fill out a bunch of information. In React.js you will just have a state that holds all of this information:

state = {username: '',password: '',fname: '',lname: '',age: '',email: '',bio: '',image: '',errors: []}

As the user fills out the information the state gets updated to hold the users input. The input fields in your form will stay the same for your cookie cutter information, but when it comes to the HTML tag for file uploads it will look something like this:

<label>Profile Image</label><inputtype="file"accept="image/jpeg" /// for imagesonChange={this.handleFileUpload}/>accept=".mp3,audio/*" /// for sound files\\\\\\\\ File upload function will look like this \\\\\\\\\\\handleFileUpload = (e) => {this.setState({   image: e.target.files[0]})}

When uploading files, it will be stored in an array. Since we are only accepting one file upload, it will always be at the zeroth position.

Your ‘handleSubmit’ function will do all of the heavy lifting of packaging up your state and sending it off to your backend. To send files over to the backend you need to use FormData. Soo, your function will have something that look like this in it:

handleSubmit = (e) => {e.preventDefault()const formData = new FormData()for (const property in this.state) {  formData.append(    property, this.state[property]  )}

Let’s break this down, since there’s a lot going on.

  1. We are preventing default (so that the page doesn’t refresh after sending a form.)
  2. We are creating new FormData, which pretty much looks like an empty object. To take a deeper dive checkout this link.
  3. This is my gift to you. This for … in loop neatly packages up everything in your state with the key value pairs of your state. ( username: ‘Iggs’) including the file the user uploaded. FormData is quite difficult to work with since, from my experience there is no way to peer inside of it. Console.log will return an empty object, debugger will only view inside an empty object (when it very clearly holds all of the information from the state.) So in other words it’s a lot of guessing and checking if it’s your first time working with FormData.

Now the only thing that’s left to do is, sending this formData to the backend:

axios.post("http://localhost:3000/users", formData)

Boom, ezpz. Now going back to the backend you controller needs to be able to accept this file upload. But it’s no sweat because Rails is the best. In your controller that you are trying to attach this file to, you’ll need to permit them in your params:

params.permit(:username, :password, :fname, :lname, :age, :email, :bio, :image)

That’s about it.

“But Ignas, how do I send this information from the backend to the frontend?”

Thanks for asking, that isn’t to hard either if you’ve added the serializer gem mentioned before. Your serializer file will look something like this:

class UserSerializer < ActiveModel::Serializerattributes :id, :email, :fname, :lname, :bio, :age, :username, :image
include
Rails.application.routes.url_helpers
def image rails_blob_path(object.image, only_path: true) if object.image.attached?endend

Now when you fetch information about users from the backend it will send everything shown above that ‘attributes’ points to. The ‘image’ that will be sent down will be a super long string that you need to plop onto a url. Like so:

const imageUrl = "/rails/active_storage/blobs/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBc3dCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--4b10997f040ab8eab876424dadcae0c4cd8caa14/20181224_113217.jpg"const link = `http://localhost:3000${imageUrl}`<img src={link}>

That’s about it. Thank you for taking the time to read this, I really do hope this helped out.

--

--

Ignas Butautas
The Startup

An aspiring coder, trying to make sense of The World (of coding.)