📖

webpackerを使ったRails6.1アプリへのReact18を導入

2023/01/21に公開

'react-rails'gemを使うことなく、webpackerの機能だけを用いて、Reactを使えるようしてみたので、簡単にまとめていきます。

検証環境

Ruby 3.0.4
Rails 6.1.7
React 18.2.0


(参考にした資料)

youtubeのものは、tutorial形式かつ、Railsプロジェクトでview部をreactのみで作成しているので、大変参考になりました。
https://www.youtube.com/watch?v=F0xErjOtJAQ
https://learnetto.com/blog/react-rails

webpackerをつかってReactを使えるようにする。

今から新規で作成するプロジェクトの場合、ReactとRailsのアプリを分離して作成した上で、API連携させることになることが多いのかと思いますので、今回は、既存のプロジェクトであるという前提で進めます。

reactの導入

$ rails webpacker:install:react

cf)

もし、新規アプリの場合
$ bin/rails new <app名> --webpack=react

これによって、react関連のパッケージの導入と、app/javascript/packs/hello_react.jsxファイルが生成されます。

<%= javascript_pack_tag ‘hello_react’ %>

layoutの<head>内にjavascript_pack_tag ‘hello_react’を記述することで、railsアプリが、hello_react.jsxファイル内のコンポーネントをレンダーするようになります。

コントローラを作成して、Helloコンポーネントがレンダーされるか確認する

実際のviewに反映されているかを、テスト用のcontrollerを追加して確認します。

$ rails g controller site index

“/”へのアクセスで今作成したページが表示されるようにルーティングを変更します。

# config/routes

Rails.application.routes.draw do
  get 'site/index'
	root 'site#index'
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end

以上で、コンポーネントを無事描画できるようになっているはずです。
(HelloコンポーネントはHello React!を出力するだけのコンポーネントです)
site#indexのview

問題が残っていた。

特に問題はないと思いながらも、ブラウザのコンソールを開くと、

Warning: ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://reactjs.org/link/switch-to-createroot

とエラーが吐かれてしまいました。どうやら、デフォルトのhello_react.jsxの記法がReact 17流の書き方だそうです。特に、ReactDOM.renderの部分。
(僕がreactに入門したのはすでに18になっていたので、違いについてはあまりよくわかっていません。)

React18流に、hello_react.jsxを変更する

生成されたhello_react.jsxは、以下のようになっています。

:before
// hello_react.jsx

import React from 'react'
import ReactDOM from 'react-dom'
import PropTypes from 'prop-types'

const Hello = props => (
  <div>Hello {props.name}!</div>
)

Hello.defaultProps = {
  name: 'David'
}

Hello.propTypes = {
  name: PropTypes.string
}

document.addEventListener('DOMContentLoaded', () => {
  ReactDOM.render(
    <Hello name="React" />,
    document.body.appendChild(document.createElement('div')),
  )
})

エラーのいうことを聞きながら変更を加えると以下のようになりました。

:after
// hello_react.jsx

import React from 'react'
import ReactDOM from 'react-dom/client'

const Hello = props => (
  <div>Hello {props.name}!</div>
)

const root = ReactDOM.createRoot(document.getElementById('app'));

root.render(
  <Hello name="React" />,
);

ここままだと、#=”app”を持つHTML要素はどこにもないので、application.html.erbに空のdivタグを追加します。

# app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>App</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag "hello_react" %>
  </head>

  <body>
    <div id="app"></div>
    <%= yield %>
  </body>
</html>

この<div>要素がReactをマウントするrootとして機能するようになります。

あとは、javascript_pack_tagを埋め込むだけです。
ページ全体をreactをつかって描画したい場合には、

e.g.)
<body>
    <div id="app"></div>
    <%= javascript_pack_tag 'hello_react' %>
    <%= yield %>
  </body>

アプリの一部だけをreactにしたい場合には、そのページにて

React要素をマウントするroot(<div id="app"></div>)を設置し、それに続くようにjavascript_pack_tag メソッドで、コンポーネントをレンダーするようにして下さい。

蛇足

その後、スタイリングがあまり得意ではないので、ChakraUIに頼ろうとしましたが、依存性があるパッケージを全て導入したような気がするものの、何故か使えませんでした...
React on Railsで導入する際には、色々と苦労することが多そうです。

Discussion