やっはろー、へっぽこだよ。
Reactでファイルのアップロードをする機能を作ったら案外手こずったから記事にしてみました。
バックエンドはRailsでS3にファイルを格納してます。
ReactからファイルデータをRailsへ飛ばす
まずファイルを添付するボタンを作ります。
色々ありますが、今回はMaterial-UIを採用しました。
おしゃれかつ痒い所まで手が届く機能を備えてます。
まずはMaterial-UIをインストールします。
yarn add @mui/material @emotion/react @emotion/styled
次に以下のコードをコピペしてください。
import React,{useMemo} from "react";
import Input from "@mui/material/Input";
export default function APP() {
const [file, setFile] = React.useState<File | string>();
const handleChangeFile = useMemo(
() => (e: any) => {
const file = e.target.files[0];
setFile(file);
},
[file]
);
return (
<label htmlFor="contained-button-file">
<Input
id="contained-button-file"
type="file"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
handleChangeFile(e);
}}
/>
</label>
);
}
コードサンドボックス:サンプル
そうすると↑を見て貰えば分かる通り、ファイル添付ボタンが現れたと思います。
仕組みを説明すると、添付ファイルを添付すると
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
handleChangeFile(e);
}}
↑が発火し
const handleChangeFile = useMemo(
() => (e: any) => {
const file = e.target.files[0];
setFile(file);
},
[file]
);
↑が実行され、選択した一番目のファイルがsetFile
によってfile
に格納されます。
それでfile
をRailsに飛ばしておkって具合でいけばいいのですが、このままだとRailsがファイルを認識しません。
file
をファイル形式にコンバートしないといけないのです。
以下のコードを追加します。
import React,{useMemo} from "react";
import Input from "@mui/material/Input";
export default function APP() {
const [file, setFile] = React.useState<File | string>();
const handleChangeFile = useMemo(
() => (e: any) => {
const file = e.target.files[0];
setFile(file);
},
[file]
);
+ const createFormData = () => {
+ const formData = new FormData();
+ formData.append("user[file]", file!);
+ return formData;
+ };
return (
<label htmlFor="contained-button-file">
<Input
id="contained-button-file"
type="file"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
handleChangeFile(e);
}}
/>
</label>
);
}
formData.append
によってfile形式にコンバートされます。
formData.append
の引数は
formData.append("Railsのテーブル名[カラム名]", ファイルが格納された変数);
にします。
テーブル名はs抜きなので注意してください。
次にaxiosでRailsにデータを飛ばします。
yarn add axios
axiosをインストール。
以下のコードを追加してください。
import React, { useMemo } from "react";
import Input from "@mui/material/Input";
+ import Button from "@mui/material/Button";
+ import axios from "axios";
export default function APP() {
const [file, setFile] = React.useState<File | string>();
const handleChangeFile = useMemo(
() => (e: any) => {
const file = e.target.files[0];
setFile(file);
},
[file]
);
const createFormData = () => {
const formData = new FormData();
formData.append("user[file]", file!);
return formData;
};
+ const postFile = async () => {
+ const data = createFormData();
+ await axios
+ .post(`http://localhost:3000/post_file/`, data)
+ .then((response) => {
+ //成功したときの処理
+ })
+ .catch(() => {
+ //失敗したときの処理
+ });
+ };
return (
<label htmlFor="contained-button-file">
<Input
id="contained-button-file"
type="file"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
handleChangeFile(e);
}}
/>
+ <Button
+ size="medium"
+ onClick={() => {
+ postFile();
+ }}
+ >
+ Railsにファイルを送信
+ </Button>
</label>
);
}
コードサンドボックス:サンプル
コードを追加したら↑と同じ感じになっていると思います。
ボタンを押したらpostFile
が発火して、axiosが動くという具合になっています。
const data = createFormData();
ここでコンバートされたファイルをdata
に格納して
.post(`http://localhost:3000/post_file/`, data)
こういう感じでrailsにデータを送信します。
本番の場合はもちろんhttp://localhost:3000
の部分が変わります。
ご自身の本番のURLを入れてください。
細かいaxiosの使い方は他記事を参考にしてください。
Railsでデータを受け取る
デフォルトでRailsは別のポートからpostリクエストを受け付けていません。
なので、rack-cors
を使って許可します。
+ gem 'rack-cors'
gemfileに↑を追加してbundle install
を実行してください。
次にconfig/initializers
配下にcors.rb
を作成します。
設定ファイルの中身は↓みたいな感じ。
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins 'http://localhost:3001'
resource '*',
headers: :any,
expose: %w[access-token expiry token-type uid client],
methods: %i[get post put patch delete options head],
credentials: true
end
end
origins
部分はRailsが使用しているポート部分です。
デフォルトは3000ですが、へっぽこはReactを3000にしてるので、Railsは3001です。
本番の場合も同じで、originsにドメインを入れます。
次にcarrierwave
とfog-aws
とをインストールします。
gem 'rack-cors'
+ gem 'carrierwave'
+ gem 'fog-aws'
carrierwave
はアップロード、ダウンロードなどをよしなににやってくれる、便利なやつです。
bundle exec rails g uploader File
を実行してapp/uploaders/file_uploader.rb
を作成。
class FileUploader < CarrierWave::Uploader::Base
# Include RMagick or MiniMagick support:
# include CarrierWave::RMagick
# include CarrierWave::MiniMagick
# Choose what kind of storage to use for this uploader:
# if Rails.env.production?
# storage :fog
# else
# storage :file
# end
if Rails.env.development?
storage :file
else
storage :fog
end
# Override the directory where uploaded files will be stored.
# This is a sensible default for uploaders that are meant to be mounted:
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
# Provide a default URL as a default if there hasn't been a file uploaded:
# def default_url(*args)
# # For Rails 3.1+ asset pipeline compatibility:
# # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))
#
# "/images/fallback/" + [version_name, "default.png"].compact.join('_')
# end
# Process files as they are uploaded:
# process scale: [200, 300]
#
# def scale(width, height)
# # do something
# end
# Create different versions of your uploaded files:
# version :thumb do
# process resize_to_fit: [50, 50]
# end
# Add a white list of extensions which are allowed to be uploaded.
# For images you might use something like this:
# def extension_whitelist
# %w(jpg jpeg gif png)
# end
# Override the filename of the uploaded files:
# Avoid using model.id or version_name here, see uploader/store.rb for details.
# def filename
# "something.jpg" if original_filename
# end
end
↑みたいな感じにします。
if Rails.env.development?
storage :file
else
storage :fog
end
storage :file
だとアップロードしたファイルはpublic/uploads
配下に保存されます。
storage :fog
だとS3に保存されます、fog-aws
のおかげで。
上記はローカルのときはpublic/uploads
配下、本番の時はS3に保存されるように設定しています。
envはこんな感じで設定しています。
Railsのルートディレクトリに置くやつですね。
RAILS_ENV = "development"
本番だったらこれをProductionに変えるだけです。
S3に保存するためにconfig/initializers
配下にcarrierwave.rb
を作成します。
require 'carrierwave/storage/abstract'
require 'carrierwave/storage/file'
require 'carrierwave/storage/fog'
CarrierWave.configure do |config|
config.storage :fog
config.fog_provider = 'fog/aws'
config.fog_directory = 'バケット名' # 作成したバケット名を記述
config.fog_credentials = {
provider: 'AWS',
aws_access_key_id: ENV['AWS_ACCESS_KEY_ID'], # 環境変数
aws_secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'], # 環境変数
region: 'ap-northeast-1', # アジアパシフィック(東京)を選択した場合
path_style: true
}
end
設定は↑みたいな感じ。
詳しいことは
【Rails】 CarrierWaveチュートリアル
をご覧ください。
マイグレーションでUserテーブルとFileカラムを作ります。
rails g migration User
class User < ActiveRecord::Migration[7.0]
def change
create_table :users do |t|
t.string 'file'
t.timestamps
end
end
end
rails db:migrate
次にモデルを作成して、CarrierWaveを紐づけます。
rails g model User
class User < ApplicationRecord
mount_uploader :file, FileUploader
end
これでfile_uploader.rb
に紐づきます。
次にルートを設定します。
Rails.application.routes.draw do
post '/post_file' => 'users#post_file'
end
コントローラーを作成します。
rails g controller user
class UsersController < ApplicationController
def post_file
User.create(file: params[:user][:file])
render status: 200
end
end
これでaxiosで.post("http://localhost:3000/post_file/", data)
でリクエストしたとき、user_controller
がpost_file
のアクションを呼び出し、paramsに入ったfileをfileカラムに保存し、Userを作成します。
fileカラムはCarrierWaveに紐づいているのでファイル形式に再度コンバートされ、carrierwave.rb
の設定に応じて保存先に保存されるという感じになります。
なんか不備あったらコメント欄で教えてください。
以上へっぽこでした。