-
Set the Rails API up as described in this guide
-
Create a React client app using
$ create-react-app
inside the root folder of the Rails app -
Set up local servers using the Foreman gem
- add
gem foreman
in the development group of your Gemfile and run command$ bundle install
- Create a
Procfile.dev
file and add the following lines so that both the rails server and the react server start (on different ports) when running$ foreman start -f Procfile.dev
:
web: sh -c 'cd client && PORT=3001 npm start' api: bundle exec rails s -p 3000
- Optionally, create a
rake.start
file in thelib/tasks
folder, to have a nicer command to start both servers. Add the following lines torake.start
:
namespace :start do task :development do exec 'foreman start -f Procfile.dev' end desc 'Start production server' task :production do exec 'NPM_CONFIG_PRODUCTION=true npm run postinstall && foreman start' end end desc 'Start development server' task :start => 'start:development'
- add
Following this tutorial frome (Sitepoint)[https://www.sitepoint.com/react-rails-5-1/]
- We first need to add the rack-cors gem to our
Gemfile
:
gem 'rack-cors', :require => 'rack/cors'
- We allow requests with certain origin to our API by adding the following bit to
config/application.rb
config.middleware.insert_before 0, Rack::Cors do
allow do
origins 'http://localhost:3000'
resource '*', :headers => :any, :methods => [:get, :post, :put, :delete, :options]
end
end
- Install the React router inside the
client/
folder using$ npm install --save react-router-dom
(the --save flag is to persist the package as a dependency inpackage.json
- Change the
index.js
file of the client app to look like this:
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import './index.css';
import App from './components/App';
ReactDOM.render((
<BrowserRouter>
<App />
</BrowserRouter>
), document.getElementById('root'));
- To setup the routing, 2 main components can be used:
<Link>
will modify the URL. can be used so that the link as a different style if the current path matches the link path. More info<Route>
will tell the app which view should be rendered in a given area of the UI for a given URL. Hence, the route component should be placed where a given component is expected to render. More info
To manipulate routes in redux reducers, it is necessary to use a specific router component compatible with react-router-dom
, and that is provided by connected-react-router
library:
- Install the package:
$ npm install --save connected-react-router
- Create a history object. That's the history object that will show in our redux store.
- Wrap the root reducer with connectRouter and supply the history object to get a new root reducer that will be able to talk with the ConnectedRouter component
- Use routerMiddleware(history) to dispatch history actions.
- Add the
ConnectedRouter
component inside theProvider
component.
import { createBrowserHistory } from 'history'
import { applyMiddleware, compose, createStore } from 'redux'
import { connectRouter, routerMiddleware, ConnectedRouter } from 'connected-react-router'
...
const history = createBrowserHistory()
const store = createStore(
connectRouter(history)(rootReducer), // new root reducer with router state
initialState,
compose(
applyMiddleware(
routerMiddleware(history), // for dispatching history actions
// ... other middlewares ...
),
),
)
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}> { /* place ConnectedRouter under Provider */ }
<div> { /* your usual react-router v4 routing */ }
<Switch>
<Route exact path="/" render={() => (<div>Match</div>)} />
<Route render={() => (<div>Miss</div>)} />
</Switch>
</div>
</ConnectedRouter>
</Provider>,
document.getElementById('react-root')
)
In case the parent component of the routes is using connect to access the redux store, we need to use a special function provided by React Router so that the Routes work (if not, they will not render the component they are supposed to, but not returning an error). This function is withRouter
. Details about implementation
- Install the preprocessor recommended by React:
$ npm install --save node-sass-chokidar
- Rename the source css files with
scss
extension - Install npm-run-all to run all the scripts in
package.json
. We will add scripts so that our scss files are watched and reprocessed on an ongoing basis. Run the command:$ npm install --save npm-run-all
- Add the scripts to
package.json
(+ means the line needs to be added, - means it needs to be removed):
+ "build-css": "node-sass-chokidar src/ -o src/",
+ "watch-css": "npm run build-css && node-sass-chokidar src/ -o src/ --watch --recursive",
- "start": "react-scripts start",
- "build": "react-scripts build",
+ "start-js": "react-scripts start",
+ "start": "npm-run-all -p watch-css start-js",
+ "build-js": "react-scripts build",
+ "build": "npm-run-all build-css build-js",
Links to the CDN:
CSS
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
JS
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
Redux store
- Select the Provider component in React tab
- In the console:
$r.store.getState();