- Create simple Rails 5 API
- Create React app use create-react-app
Install dependencies:
cd && git clone https://github.com/ntamvl/react-upload-progress-bar-with-rails-5-api.git
cd && cd react-upload-progress-bar-with-rails-5-api/media_api && bundle install
cd && cd react-upload-progress-bar-with-rails-5-api/media_client && yarn install
Run API:
Edit config/database.yml
before run this commands.
cd && cd react-upload-progress-bar-with-rails-5-api/media_api
rails db:create && rails db:migrate
rails s -b 0.0.0.0 -p 3003
Run Client:
cd && cd react-upload-progress-bar-with-rails-5-api/media_client
yarn start
Setup new rails 5 project:
rails _5.0.1_ new media_api -d postgresql
Edit Gemfile:
# ...
gem 'rack-cors', '0.4.0'
gem 'paperclip', '5.0.0.beta2'
gem 'bootstrap', '~> 4.0.0.alpha5'
# ...
Edit config/database.yml
:
default: &default
adapter: postgresql
encoding: unicode
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
template: template0
host: localhost
username: postgres
password: [password to access PG]
Install the gems:
bundle install
Go to the application configuration file and add the configurations for CORS:
# config/application.rb
config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource '*', :headers => :any, :methods => [:get, :post, :options]
end
end
Generate model view controller Item
:
rails g scaffold Item name:string description:string
Generate a migration that will add the attachment to the database use paperclip
:
rails g paperclip item picture
Create the database if not exists:
rails db:create
Migrate the database:
rails db:migrate
Result:
== 20170103114249 CreateItems: migrating ======================================
-- create_table(:items)
-> 0.0149s
== 20170103114249 CreateItems: migrated (0.0149s) =============================
== 20170103114304 AddAttachmentPictureToItems: migrating ======================
-- change_table(:items, {})
-> 0.0020s
== 20170103114304 AddAttachmentPictureToItems: migrated (0.0021s) =============
Add the following lines in the file of the model Item
:
has_attached_file :picture, styles: { medium: "300x300>", thumb: "100x100>" }, default_url: "/images/:style/missing.png",
url: "/media/:id/:style/:hash.:extension",
path: ":rails_root/public/media/:id/:style/:hash.:extension",
hash_secret: "tamtam"
validates_attachment :picture, presence: true
do_not_validate_attachment_file_type :picture
Add controller upload in app/controller/items_controller.rb
:
def upload
data = {
name: params[:name],
description: params[:description],
picture: params[:picture]
}
item = Item.new(data)
if item.save
render json: {
item: {
name: item.name,
description: item.description,
picture_url: item.picture.url
},
status: 200
}
else
render json: { message: "Something went wrong.", status: 400 }
end
end
Add a permitted parameter that is going to accept :picture
:
def item_params
params.require(:item).permit(:name, :description, :picture)
end
Add routes /upload
in config/routes.rb
:
Rails.application.routes.draw do
resources :items
post '/upload' => 'items#upload'
end
Add :verify_authenticity_token
to application_controller.rb
:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
skip_before_action :verify_authenticity_token
end
Rename application.css
to application.scss
in app/assets/stylesheets
and add this line:
@import "bootstrap";
Add this line to app/assets/javascripts/application.js
:
//= require jquery
//= require jquery_ujs
//= require bootstrap-sprockets
//= require turbolinks
//= require_tree .
Update app/views/layouts/application.html.erb
:
<!DOCTYPE html>
<html>
<head>
<title>DemoUpload1</title>
<%= csrf_meta_tags %>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
</head>
<body>
<div class="container">
<div class="row">
<%= yield %>
</div>
</div>
</body>
</html>
Run Rails 5 API app on port 3003
:
rails s -b 0.0.0.0 -p 3003
To install create-react-app
, please read at https://facebook.github.io/react/blog/2016/07/22/create-apps-with-no-configuration.html
Create simple React App:
create-react-app media_client
Install packages:
npm i react-dropzone react-redux redux redux-form axios rc-progress node-uuid bootstrap jquery --save
Add jquery and bootstrap to index.js
:
import 'bootstrap/dist/css/bootstrap.css';
import jquery from 'jquery';
window.$ = window.jQuery=jquery;
require('bootstrap/dist/js/bootstrap');
Add Upload components in src/components
:
import React, { Component } from 'react';
import Dropzone from 'react-dropzone';
import { Field, reduxForm } from 'redux-form';
import { post } from 'axios';
import { Line, Circle } from 'rc-progress';
import { v4 } from 'node-uuid';
class Upload extends Component {
constructor(props) {
super(props);
this.onDrop = this.onDrop.bind(this);
this.onOpenClick = this.onOpenClick.bind(this);
this.onFormSubmit = this.onFormSubmit.bind(this);
this.state = {
files: [],
percentCompleted: 0
}
}
onFormSubmit(data) {
const url = '/upload';
let formData = new FormData();
formData.append('name', data.name)
formData.append('description', data.description)
formData.append('picture', data.picture)
console.log("formData", formData);
const config = {
headers: { 'content-type': 'multipart/form-data' },
onUploadProgress: function(progressEvent) {
var percentCompleted = Math.round( (progressEvent.loaded * 100) / progressEvent.total );
this.setState({ percentCompleted: percentCompleted });
}.bind(this)
}
post(url, formData, config)
.then(function(response) {
console.log(response);
})
.catch(function(error) {
console.log(error);
});
}
onDrop(acceptedFiles) {
this.setState({
files: acceptedFiles
});
console.log("onDrop", this.state.files);
acceptedFiles.forEach((file)=> {
const data = {
name: "dora1",
description: "dora1 description",
picture: file
}
console.log("file", JSON.stringify(data));
this.onFormSubmit(data);
});
}
onOpenClick() {
this.dropzone.open();
console.log("onOpenClick", this.state.files);
}
renderThumb(file, idx) {
return (
<div className="col-md-2" key={ v4() }>
<img key={ v4() } src={file.preview} className="img-thumbnail" />
</div>
)
}
render() {
const progress = this.state.percentCompleted;
return (
<div className="col-md-12">
<div className="row">
<Dropzone ref={(node) => { this.dropzone = node; }} onDrop={this.onDrop}>
<div>Try dropping some files here, or click to select files to upload.</div>
</Dropzone>
<div className="pull-left">
<br />
<button type="button" className="btn" onClick={this.onOpenClick}>
Open files
</button>
</div>
</div>
<div className="row">
<p>{progress} %</p>
<Line percent={progress} strokeWidth="4" strokeColor="#00ff00" />
{/* <Circle percent={progress} strokeWidth="4" strokeColor="#D3D3D3" /> */}
</div>
{this.state.files.length > 0 ? <div>
<h2>Uploading {this.state.files.length} files...</h2>
<div className="row">
{this.state.files.map((file, idx) => this.renderThumb(file, idx) )}
</div>
</div> : null}
</div>
)
}
}
export default Upload
Add proxy in package.json
point to rails api:
"proxy": "http://localhost:3003",
Run React app:
yarn start