This article describes how to start React on Rails.
π» Instration Node with Mac Homebrew brew upgrade brew install node
πΌ Creating React Sample App npm install -g create-react-app create-react-app hello-world cd hello-worldnpm start
π Using React on Rails In this section, we are using RubyGem react_on_rails .
At first, add the following to your Gemfile and bundle install
:
Commit this to git and please run following commands:
rails g react_on_rails:install bundle && npm install foreman start -f Procfile.dev
Please see http://localhost:3000/hello_world .
All JavaScript in React On Rails is loaded from npm: react-on-rails. To manually install this, please execute following command like this:
cd client && npm i --saveDev react react-on-rails react-helmet nprogress
π‘ Controller Supporting following flow:
Return HTML in first request
In page transition, return JSON
Do not show JSON when returning with browser back
class ApplicationController < ActionController::Base protect_from_forgery with: :exception private def _action_path "#{controller_path} ##{action_name} " end def _common_props { actionPath: _action_path } end def _render_for_react (props: {}, status: 200 ) if request.format.json? response.headers['Cache-Control' ] = 'no-cache, no-store' response.headers['Expires' ] = 'Fri, 01 Jan 1990 00:00:00 GMT' response.headers['Pragma' ] = 'no-cache' render( json: { rootProps: _common_props.merge(props) }, status: status ) else render( html: view_context.react_component( 'Router' , props: { rootProps: _common_props.merge(props) }.as_json ), layout: true , status: status ) end end end
Usage example is as follows:
class ArticlesController < ApplicationController def index _render_for_react( props: { articles: Article.limit(20 ) } ) end end
πΈ JavaScript Entry Point Entry point of shared JavaScript code is like this:
import ReactOnRails from "react-on-rails" ;import Router from "../components/Router" ;ReactOnRails.register({ Router });
Router
Router selects Component by actionPath
Display progress bar during page transition
Scroll to a top of a page after page transition
Adjust browser history using pushState / popState
import React from "react" ;import NProgress from "nprogress" ;import Articles from "../components/Articles" export default class Router extends React .Component { static propTypes = { rootProps: React.PropTypes.object, }; static childContextTypes = { onLinkClick: React.PropTypes.func, }; componentDidMount() { window .addEventListener("popstate" , () => { this .transitTo(document .location.href, { pushState : false }); }); } constructor (...args) { super (...args); this .state = { rootProps: this .props.rootProps, }; }; getComponent() { switch (this .state.rootProps.actionPath) { case "articles#index" : return Articles; } }; getChildContext() { return { onLinkClick: this .onLinkClick.bind(this ), }; }; onLinkClick(event) { if (!event.metaKey) { event.preventDefault(); const anchorElement = event.currentTarget.pathname ? event.currentTarget : event.currentTarget.querySelector("a" ); this .transitTo(anchorElement.href, { pushState : true }); } }; transitTo(url, { pushState }) { NProgress.start(); $.ajax({ url: url, dataType: 'json' , cache: false , success: function (props ) { if (pushState) { history.pushState({}, "" , url); } this .setState({rootProps : props.rootProps}); NProgress.done(); if (typeof window !== 'undefined' ) { window .scrollTo(0 , 0 ); } }.bind(this ), error: function (xhr, status, err ) { NProgress.done(); console .error(url, status, err.toString()); }.bind(this ) }); }; render() { const Component = this .getComponent(); return <Component {...this.state.rootProps } key ={this.state.requestId} /> ; }; }
Link When a user clicks a link, a client communicates with the server with XHR and post a state to Router.
So I use the original Link tag instead of a tag.
import React from "react" ;export default class Link extends React .Component { static contextTypes = { onLinkClick: React.PropTypes.func, }; onClick(event) { this .context.onLinkClick(event); } render() { return ( this .onClick.bind(this )} {...this.props}> {this .props.children} </a> ); } };
Link
example is as follows:
π£ Title/Meta Tags import React from "react" ;import Helmet from "react-helmet" ;import {siteName, siteBaseUrl} from "../constants/service" ;export default class Articles extends React .Component { pageUrl() { return siteBaseUrl + "/articles" ; } pageTitle() { return "huga" ; } pageDescription() { return "hoge" ; } render() { return (
title={this .pageTitle()} link={[ {rel : "canonical" , href : this .pageUrl()}, {rel : 'alternate' , href : this .pageUrl()}, ]} meta={[ {property : "og:url" , content : this .pageUrl()}, {property : "og:title" , content : this .pageTitle()}, {property : "og:description" , content : this .pageDescription()}, {name : "description" , content : this .pageDescription()}, ]} /> </div> ); } }
π Run npm install in ElasticBeansTalk This is a script to use webpack
in deploy process of ElasticBeansTalk. To use webpack you need to run npm install
beforerake assets: precompile
. (10
is depend on each environment, so please fix it.)
files: "/opt/elasticbeanstalk/hooks/appdeploy/pre/10_install_node_modules.sh" : mode: "000744" owner: root group: root content: | #!/usr/bin/env bash set -xe EB_APP_STAGING_DIR=$(/opt/elasticbeanstalk/bin/get-config container -k app_staging_dir) cd $EB_APP_STAGING_DIR/client npm install encoding: plain
π Special Thanks This article has created by the following Japanese articles. Thank you very much, @r7kamura !
π Sample App
π₯ Recommended VPS Service
VULTR provides high performance cloud compute environment for you.
Vultr has 15 data-centers strategically placed around the globe, you can use a VPS with 512 MB memory for just $ 2.5 / month ($ 0.004 / hour).
In addition, Vultr is up to 4 times faster than the competition, so please check it => Check Benchmark Results !!