Thursday, January 28, 2021

Image Uploading

Image and file uploading from a client to a server is a really typical thing you'll need to setup in a web app. Here is a quick implementation guide on how to achieve this with React and Rails.

For some context the app allows users to add dog's with a name and image.

  1. Create a component that renders a form with an input file and submit, something like this, note that when the inputs change new states are set:

    import React from "react";
    const [file, setFile] = useState("");
    const [name, setName] = useState("");
    
    function App() {
      return (
        <form>
          <label htmlFor="name">Name</label>
          <input
            type="text"
            name="name"
            id="name"
            value={name}
            onChange={(e) => setName(e.target.value)}
          />
          <label htmlFor="file">Dog image</label>
          <input
            type="file"
            name="file"
            id="file"
            onChange={(e) => {
              setFile(e.target.files[0]);
            }}
          />
        </form>
      );
    }
    
  2. Set the encType attribute of the form to be multipart/form-data, this ensures the fetch request is sent with this content type that is needed for files, also add an onSubmit event listener with a function that is bound to it

    <form encType="multipart/form-data" onSubmit={onFormSubmit}></form>
    
  3. Configure the onFormSubmit function, this function creates a form data object that is sent in the body of the request, we have to use form data here as we're dealing with files, JSON wouldn't work, we make a POST request to an endpoint we'll configure soon in rails and we await its response

    async function onFormSubmit(e) {
      e.preventDefault();
      const formData = new FormData();
      formData.append("file", file);
      formData.append("name", name);
      try {
        const response = await fetch("http://localhost:3000/dogs", {
          method: "POST",
          body: formData,
        });
        console.log(response);
      } catch (err) {
        console.log(err);
      }
    }
    
  4. Shifting to rails create the db and the Dog model that we need:

    rails db:create
    rails g model Dog name
    rails db:migrate
    
  5. Install active storage

    bin/rails active_storage:install
    
  6. Add the active storage relation to the Dog model

    has_one_attached :image
    
  7. Create a dogs controller

    rails g controller dogs
    
  8. Add an index and create action to routes

    resources :dogs, only: [:index, :create]
    
  9. Add the controller actions we need to the dogs_controller, we use the url_for method to extract the url from the active storage relation

    class DogsController < ApplicationController
      def index 
        dogs = Dog.all.map do |dog|
          {
            name: dog.name,
            image_url: url_for(dog.image)
          }
        end
        render json: dogs
      end
    
      def create
        dog = Dog.new(name: params[:name])
        if dog.save
          dog.image.attach(params[:file])
          render status: :ok
        else 
          render status: :bad_request
        end    
      end
    end
    
  10. Deal with cors, uncomment rack-cors from your gemfile and whitelist your client side url in the cors.rb file

  11. Your ready to now add an image and submit it on the client

  12. To see all of your dogs in an array you could open postman and make a GET request to http://localhost:3000/dogs