DEV Community

andrewjpwalters
andrewjpwalters

Posted on

Using Active Storage with Amazon S3 and React

In this blog post, we'll explore how to use Active Storage with Amazon S3 to store image files and how to use React to display those images in a web application.

Setting up Amazon S3

The first step is to create an Amazon S3 bucket to store your image files. To do this, log in to your Amazon Web Services (AWS) account and navigate to the S3 dashboard. From there, click on the "Create bucket" button and follow the prompts to create your bucket.

Once your bucket is created, you'll need to generate an access key and secret key for your AWS account. These credentials will be used by Rails to access your S3 bucket. To generate your credentials, navigate to the AWS Identity and Access Management (IAM) dashboard and create a new user. Make sure to give the user the appropriate permissions to access your S3 bucket.

Once your bucket is set up, you will also want to add the following permission object into the CORS (Cross-origin resource sharing) configuration in your bucket settings:

[
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "PUT",
            "POST",
            "DELETE"
        ],
        "AllowedOrigins": [
            "*"
        ],
        "ExposeHeaders": []
    }
]
Enter fullscreen mode Exit fullscreen mode

Setting up Active Storage

The next step is to set up Active Storage in your Rails application. To do this, first, add the following line to your Gemfile:

gem 'aws-sdk-s3'
Enter fullscreen mode Exit fullscreen mode

This will install the necessary dependencies for using Active Storage with Amazon S3.

Next, run the following command to set up Active Storage:

rails active_storage:install
Enter fullscreen mode Exit fullscreen mode

This will generate a migration that adds the necessary tables to your database.

Run the migration with the following command:

rails db:migrate
Enter fullscreen mode Exit fullscreen mode

You will also need to uncomment Amazon storage code in your storage.yml file and fill in the appropriate data:

amazon:
  service: S3
  access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
  secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
  region: your-region
  bucket: your-bucket-name
Enter fullscreen mode Exit fullscreen mode

Next, with this command you will be able to edit your credential files and include the access key and secret access key provided by AWS:

EDITOR="code --wait" rails credentials:edit
Enter fullscreen mode Exit fullscreen mode

In your development.rb file in your environments, you will also need to change your config.active_storage.service to Amazon:

config.active_storage.service = :amazon
Enter fullscreen mode Exit fullscreen mode

Finally, in your initializers, in your cors.rb file, uncomment out the necessary code, and change the origins to whatever your project requires. For the sake of this example, a wildcard will be included:

Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins '*'

    resource '*',
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end
Enter fullscreen mode Exit fullscreen mode

Now that Active Storage is set up, we can start using it to store our image files.

Uploading Files

To upload an image file, first, create a form in your React application that allows users to select a file to upload. Here's an example:

import React, { useState } from 'react';

function ImageUploadForm() {
  const [file, setFile] = useState(null);

  function handleChange(event) {
    setFile(event.target.files[0]);
  };

  function handleSubmit(event) {
    event.preventDefault();
    const formData = new FormData();
    formData.append('image', file);

    fetch('/images', {
      method: 'POST',
      body: formData,
    });

    if (response.ok) {
      console.log('Image uploaded successfully');
    } else {
      console.log('Error uploading image');
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="file" onChange={handleChange} />
      <button type="submit">Upload Image</button>
    </form>
  );
}

export default ImageUploadForm;
Enter fullscreen mode Exit fullscreen mode

This form will allow users to select an image file to upload. Note that images must be appended to a new FormData as the information can not be sent as JSON.

Next, create a Rails controller action to handle the file upload. Here's an example:

class ImagesController < ApplicationController
  def create
    image = Image.new(image_params)
    if image.save
      render json: { image_url: url_for(image.image) }, status: :created
    else
      render json: image.errors, status: :unprocessable_entity
    end
  end

  private

  def image_params
    params.permit(:image)
  end
end
Enter fullscreen mode Exit fullscreen mode

This controller action creates a new Image object and assigns it the uploaded file. The image_params method is used to permit only the image parameter to be passed to the Image model. The response includes the URL of the uploaded image, which we'll use in the React application to display the image.

Another way to access the image’s url is to define the image_url in the serializer. Here’s an example for that:

class ImageSerializer < ActiveModel::Serializer
  include Rails.application.routes.url_helpers

  attributes :id, :image_url

  def image_url
    if object.image.attached?
      url_for(object.image)
    else
      ""
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Note that the line “include Rails.application.routes.url_helpers” must be included to have access to the url_for helper.

Next, add the following code to your Image model to use Active Storage to store the image file:

class Image < ApplicationRecord
  has_one_attached :image
end
Enter fullscreen mode Exit fullscreen mode

Displaying Files

Now that we have uploaded our images to S3 using Active Storage, we can display them in our React application. Here's an example of how to display an image in React:

import React from 'react';

function ImageDisplay({ imageUrl }) {
  return (
    <div>
      <img src={imageUrl} alt="Uploaded Image" />
    </div>
  );
}

export default ImageDisplay;
Enter fullscreen mode Exit fullscreen mode

This component takes the URL of the image as a prop and displays it using an img element.

Finally, we can use our ImageUploadForm and ImageDisplay components together to create a complete image upload and display feature:

import React, { useState } from 'react';
import ImageUploadForm from './ImageUploadForm';
import ImageDisplay from './ImageDisplay';

function App() {
  const [imageUrl, setImageUrl] = useState(null);

  function handleImageUpload(formData) {
    fetch('/images', {
      method: 'POST',
      body: formData,
    });

    if (response.ok) {
      r.json().then((data) => setImageUrl(data.image_url));
    } else {
      console.log('Error uploading image');
    }
  };

  return (
    <div>
      <ImageUploadForm onImageUpload={handleImageUpload} />
      {imageUrl && <ImageDisplay imageUrl={imageUrl} />}
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

This component creates an ImageUploadForm and an ImageDisplay component. When an image is uploaded, the handleImageUpload function is called, which sends a POST request to the Rails API and sets the imageUrl state to the URL of the uploaded image. The ImageDisplay component is only shown if imageUrl is not null.

And that's it! You should now have a fully functioning image upload and display feature in your React application using Active Storage with Amazon S3.

Top comments (1)

Collapse
 
ksylvest profile image
Kevin Sylvestre

If you are looking to integrate ActiveStorage w/ React I'd suggest using a hook library to simplify the direct to provider (AWS / Google / Azure / etc) transfer of files. If you are curious I published an NPM package react-activestorage to handle that integration.