Skip to content

Latest commit

 

History

History
338 lines (250 loc) · 10.9 KB

get-started.md

File metadata and controls

338 lines (250 loc) · 10.9 KB

Get Started

Use with Shakapacker

  1. Create a new Rails app: Prevent installing default javascript dependencies by using --skip-javascript option:
rails new my-app --skip-javascript
cd my-app
  1. Install shakapacker:
bundle add shakapacker --strict
rails shakapacker:install
  1. 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"
  ]
},
  1. Install react-rails:
$ bundle add 'react-rails' --strict
$ rails generate react:install

This gives you:

  • app/javascript/components/ directory for your React components
  • ReactRailsUJS setup in app/javascript/packs/application.js
  • app/javascript/packs/server_rendering.js for server-side rendering
  1. 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." }) %>
  1. Render it in a Rails view:
<!-- erb: paste this in view -->
<%= react_component("HelloWorld", { greeting: "Hello from react-rails." }) %>
  1. Lets Start the app:
$ rails s

Output: greeting: Hello from react-rails", inspect webpage in your browser to see the change in tag props.

  1. 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.

Component name

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 via useContexts to server_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
]);

File naming

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")

Typescript support

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"]

Test component

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

Use with Asset Pipeline

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 //= requires to application.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` -->

Custom JSX Transformer

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 to config.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).

Transform Plugin Options

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']
}

React.js versions

//= 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