- Create a new Rails app:
Prevent installing default javascript dependencies by using
--skip-javascript
option:
rails new my-app --skip-javascript
cd my-app
- Install
shakapacker
:
bundle add shakapacker --strict
rails shakapacker:install
- Install
react
and some other required npm packages:
yarn add react react-dom @babel/preset-react prop-types \
css-loader style-loader mini-css-extract-plugin css-minimizer-webpack-plugin
Also update the Babel configuration in the package.json
file:
"babel": {
"presets": [
- "./node_modules/shakapacker/package/babel/preset.js"
+ "./node_modules/shakapacker/package/babel/preset.js",
+ "@babel/preset-react"
]
},
- Install
react-rails
:
$ bundle add 'react-rails' --strict
$ rails generate react:install
This gives you:
app/javascript/components/
directory for your React componentsReactRailsUJS
setup inapp/javascript/packs/application.js
app/javascript/packs/server_rendering.js
for server-side rendering
- Generate your first component:
$ rails g react:component HelloWorld greeting:string
You can also generate your component in a subdirectory:
$ rails g react:component my_subdirectory/HelloWorld greeting:string
Note: Your component is added to app/javascript/components/
by default.
Note: If your component is in a subdirectory you will append the directory path to your erb component call.
Example:
<%= react_component("my_subdirectory/HelloWorld", { greeting: "Hello from react-rails." }) %>
<!-- erb: paste this in view -->
<%= react_component("HelloWorld", { greeting: "Hello from react-rails." }) %>
- Lets Start the app:
$ rails s
Output: greeting: Hello from react-rails", inspect webpage in your browser to see the change in tag props.
- Run dev server (optional) In order to run dev server with HMR feature you need to parallely run:
$ ./bin/shakapacker-dev-server
Note: On Rails 6 you need to specify webpack-dev-server
host. To this end, update config/initializers/content_security_policy.rb
and uncomment relevant lines.
The component name tells react-rails
where to load the component. For example:
react_component call |
component require |
---|---|
react_component("Item") |
require("Item") |
react_component("items/index") |
require("items/index") |
react_component("items.Index") |
require("items").Index |
react_component("items.Index.Header") |
require("items").Index.Header |
This way, you can access top-level, default, or named exports.
The require.context
inserted into packs/application.js
is used to load components. If you want to load components from a different directory, override it by calling ReactRailsUJS.useContext
:
var myCustomContext = require.context("custom_components", true)
var ReactRailsUJS = require("react_ujs")
// use `custom_components/` for <%= react_component(...) %> calls
ReactRailsUJS.useContext(myCustomContext)
If require
fails to find your component, ReactRailsUJS
falls back to the global namespace, described in Use with Asset Pipeline.
In some cases, having multiple require.context
entries may be desired. Examples of this include:
- Refactoring a typical Rails application into a Rails API with an (eventually) separate Single Page Application (SPA). For this use case, one can add a separate pack in addition to the typical
application
one. React components can be shared between the packs but the new pack can use a minimal Rails view layout, different default styling, etc. - In a larger application, you might find it helpful to split your JavaScript by routes/controllers to avoid serving unused components and improve your site performance by keeping bundles smaller. For example, you might have separate bundles for homepage, search, and checkout routes. In that scenario, you can add an array of
require.context
component directory paths viauseContexts
toserver_rendering.js
, to allow for Server-Side Rendering across your application:
// server_rendering.js
var homepageRequireContext = require.context('homepage', true);
var searchRequireContext = require.context('search', true);
var checkoutRequireContext = require.context('checkout', true);
var ReactRailsUJS = require('react_ujs');
ReactRailsUJS.useContexts([
homepageRequireContext,
searchRequireContext,
checkoutRequireContext
]);
React-Rails supports plenty of file extensions such as: .js, .jsx.js, .js.jsx, .es6.js, .coffee, etcetera! Sometimes this will cause a stumble when searching for filenames.
Component File Name | react_component call |
---|---|
app/javascript/components/samplecomponent.js |
react_component("samplecomponent") |
app/javascript/components/sample_component.js |
react_component("sample_component") |
app/javascript/components/SampleComponent.js |
react_component("SampleComponent") |
app/javascript/components/SampleComponent.js.jsx |
Has to be renamed to SampleComponent.jsx, then use react_component("SampleComponent") |
yarn add typescript @babel/preset-typescript
Babel won’t perform any type-checking on TypeScript code. To optionally use type-checking run:
yarn add fork-ts-checker-webpack-plugin
Add tsconfig.json
with the following content:
{
"compilerOptions": {
"declaration": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": ["es6", "dom"],
"module": "es6",
"moduleResolution": "node",
"sourceMap": true,
"target": "es5",
"jsx": "react",
"noEmit": true
},
"exclude": ["**/*.spec.ts", "node_modules", "vendor", "public"],
"compileOnSave": false
}
Then modify the webpack config to use it as a plugin:
// config/webpack/webpack.config.js
const { generateWebpackConfig, merge } = require('shakapacker')
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const webpackConfig = generateWebpackConfig()
module.exports = merge(
webpackConfig, {
plugins: [new ForkTsCheckerWebpackPlugin()]
}
);
Doing this will allow React-Rails to support the .tsx extension. Additionally, it is recommended to add ts
and tsx
to the server_renderer_extensions
in your application configuration:
config.react.server_renderer_extensions = ["jsx", "js", "tsx", "ts"]
You can use assert_react_component
to test component render:
<!-- app/views/welcome/index.html.erb -->
<%= react_component("HelloWorld", { greeting: "Hello from react-rails.", info: { name: "react-rails" } }, { class: "hello-world" }) %>
class WelcomeControllerTest < ActionDispatch::IntegrationTest
test 'assert_react_component' do
get "/welcome"
assert_equal 200, response.status
# assert rendered react component and check the props
assert_react_component "HelloWorld" do |props|
assert_equal "Hello from react-rails.", props[:greeting]
assert_equal "react-rails", props[:info][:name]
assert_select "[class=?]", "hello-world"
end
# or just assert component rendered
assert_react_component "HelloWorld"
end
end
react-rails
provides a pre-bundled React.js & a UJS driver to the Rails asset pipeline. Get started by adding the react-rails
gem:
gem 'react-rails'
And then install the react generator:
$ rails g react:install
Then restart your development server.
This will:
- add some
//= require
s toapplication.js
- add a
components/
directory for React components - add
server_rendering.js
for server-side rendering
Now, you can create React components in .jsx
files:
// app/assets/javascripts/components/post.jsx
window.Post = createReactClass({
render: function() {
return <h1>{this.props.title}</h1>
}
})
// or, equivalent:
class Post extends React.Component {
render() {
return <h1>{this.props.title}</h1>
}
}
Then, you can render those components in views:
<%= react_component("Post", {title: "Hello World"}) %>
Components must be accessible from the top level, but they may be namespaced, for example:
<%= react_component("Comments.NewForm", {post_id: @post.id}) %>
<!-- looks for `window.Comments.NewForm` -->
react-rails
uses a transformer class to transform JSX in the asset pipeline. The transformer is initialized once, at boot. You can provide a custom transformer to config.react.jsx_transformer_class
. The transformer must implement:
#initialize(options)
, where options is the value passed toconfig.react.jsx_transform_options
#transform(code_string)
to return a string of transformed code
react-rails
provides two transformers, React::JSX::BabelTransformer
(which uses ruby-babel-transpiler) and React::JSX::JSXTransformer
(which uses the deprecated JSXTransformer.js
).
To supply additional transform plugins to your JSX Transformer, assign them to config.react.jsx_transform_options
react-rails
uses the Babel version of the babel-source
gem.
For example, to use babel-plugin-transform-class-properties
:
config.react.jsx_transform_options = {
optional: ['es7.classProperties']
}
//= require react
brings React
into your project.
By default, React's [development version] is provided to Rails.env.development
. You can override the React build with a config:
# Here are the defaults:
# config/environments/development.rb
MyApp::Application.configure do
config.react.variant = :development
end
# config/environments/production.rb
MyApp::Application.configure do
config.react.variant = :production
end
Be sure to restart your Rails server after changing these files. See VERSIONS.md to learn which version of React.js is included with your react-rails
version. In some edge cases you may need to bust the sprockets cache with rake tmp:clear