react-rails step by step
Environment setup
- Install yarn using
brew install yarn
on macOS. rails new -T --skip-coffee react-rails-example
cd react-rails-example
- add
gem 'webpacker'
andgem 'react-rails', '~>2.4'
toGemfile
bundle install
rails webpacker:install
rails webpacker:install:react
rails g react:install
Set up a route and controller for frontend
- add
root 'frontend#index'
to/config/routes.rb
. This can be any controller, but I'm doing a nonRESTful route because it isn't linked to a model. - generate controller
rails g controller frontend index
Generate first component, call it from view, and set up layout so everything works
- generate App javascript component
rails g react:component App
(this literally just makes a file in/app/javascript/components
called App.js and has some react boilerplate) - edit
/app/javascript/components/App.js
to have more than just an empty div. (Will provide instructions for setting up react router later)
import React from "react"
import PropTypes from "prop-types"
class App extends React.Component {
render () {
return (
<div>
I am the App Component
</div>
)
}
}
// Always remember to export your component
export default App
- add
<%= javascript_pack_tag 'application' %>
to/app/views/layouts/application.html.erb
. This step is missing from the tutorial for some reason (or it should automatically happen) - add
<%= react_component("App") %>
to/app/views/frontend/index.html.erb
Running the server
- start the webpack-dev-server, which lets you get instant reloading of the page when you edit a component. It also compiles all your React/ES6 stuff.
.bin/webpack-dev-server
- start the rails server
bin/rails s
- Visit app at http://localhost:3000
Make starting the server only one command (Optional)
- Install foreman, which let's you run two commands at once. It should not go in your
Gemfile
, so just install it usinggem install foreman
from the command line. - Create a file that specifies the commands for foreman to run. In the root directory, make a file called Procfile.dev and include the following lines
web: bundle exec rails s -p 3000
webpacker: ./bin/webpack-dev-server
- Create a file
/bin/server
and add the lines
#!/bin/bash -i
foreman start -f Procfile.dev
- Now you can start both servers by running
bin/server
from the terminal - If you got a permission error trying to run
bin/server
you can update the permissions in the terminal usingchmod 777 bin/server
Set up react-router
This is an example of how to set up an app that has a header, footer, and main content area that you can change out with three separate routes.
- add react-router and react-router-dom to
package.json
by runningyarn add react-router react-router-dom
- set up Header, Footer, AppRouter, and Main components
rails g react:component Header
rails g react:component Footer
rails g react:component AppRouter
rails g react:component Main
rails g react:component Home
rails g react:component Example
rails g react:component SomePage
- Make AppRouter the new parent component. This is necessary to make the router
work. Edit your view file so it calls
<%= react_component("AppRouter") %>
- Edit
/app/javascript/components/AppRouter.js
// /app/javascript/components/AppRouter.js
import React from "react"
import PropTypes from "prop-types"
import { BrowserRouter } from 'react-router-dom'
import App from './App'
class AppRouter extends React.Component {
render () {
return (
<BrowserRouter>
<App />
</BrowserRouter>
)
}
}
export default AppRouter
- Your app needs to live inside the BrowseRouter component. We will set up an App which will have an optional Header and Footer with a Main content component in the middle. This makes App essentially like your application layout file. The Main component will have the rest of the logic fo switching betwen frontend components
// /app/javascript/components/App.js
import React from "react"
import PropTypes from "prop-types"
import Header from './Header'
import Footer from './Footer'
import Main from './Main'
class App extends React.Component {
render () {
return (
<div>
<Header />
<Main />
<Footer />
</div>
)
}
}
export default App
- The Main component has a child component called Switch that lets you define routes inside it. Using switch means it only use the first matching route. If you only use Route components, all components that match the required setting will be rendered in order. For this example, I'm using Switch.
- Route components need a path, which defines the url that will cause the specified component prop to render. Don't forget to import all the components you are giving to your Routes.
// /app/javascript/components/Main.js
import React from "react"
import PropTypes from "prop-types"
import { Switch, Route } from 'react-router-dom'
import Example from './Example'
import SomePage from './SomePage'
import Home from './Home'
class Main extends React.Component {
render () {
return (
<div className="main-content" style={{height: '100%'}}>
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/example" component={Example} />
<Route exact path="/some_page" component={SomePage} />
<Route path="/*" render = {() => <div><h1>404 Not Found</h1></div>} />
</Switch>
</div>
)
}
}
export default Main
- The Header component has navigation links to demonstrate how everything works
together. The react router component
Link
has ato
property that specifies the new url. It can be given a style prop just like a normal anchor tag, and generates an normal anchor tag in the html.
// /app/javascript/components/Header.js
import React from "react"
import PropTypes from "prop-types"
import { Link } from 'react-router-dom'
class Header extends React.Component {
render () {
return (
<div className="header" style={{height: '200px', backgroundColor: 'honeydew'}}>
<h2>I am the header</h2>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/example">Example</Link></li>
<li><Link to="/some_page">Some Page</Link></li>
</ul>
</div>
)
}
}
export default Header
- The rest of the files can be seen in the repo
Set up the Rails router to route any unmatched routes to the js frontend
While react router will let you click links to go to new pages and let you use the back and forward browser buttons, you will get a Rails routing error if you go directly to one of the react route urls. Here is a way to fix this behavior. The downside is that you will not get a routing error unless you configure a catchall route in the react router Switch component. This is done in the example above.
- In your
/config/routes.rb
file, make the very last lineget "*path", to: 'frontend#index'
. Replace the controller to match the controller you are using to render your react front end. - TODO - Write how to do resourceful react routes like '/dogs/:id' Visit React Router to see more info on how to use it.