Ruby on Railsを勉強中に遭遇した問題(3)
前回の「Ruby on Railsを勉強中に遭遇した問題(2)」の続きになります。
テキストとして使用している「Ruby on Rails 6 超入門」の、第5章「その他に覚えておきたい機能」の「Reactとの連携」以降で遭遇したものです。
ただ、「Reactとの連携」については Rails 7 で大きく変わっているため、テキストと同じようなことを別の方法で試しています。
- 価格: 3520 円
- 楽天で詳細を見る
テキストに従ってやっていくと、テキストには書かれていない問題にいろいろ遭遇しました。
ここでは、遭遇した問題について、内容と対応を記録したいと思います。
Reactとの連携
合計を計算し表示する
テキストと同じように、ゼロから 100, 200, 300までの合計を計算し表示させてみました。
ただ、最初表示されなかったので、Helloメッセージも出力するようにしています。
ソース・コードは、以下のものです。
<h1 class="display-4 text-primary">Hello#index</h1>
<div id="hello"></div>
<div id="calc_result"></div>
import React from 'react'
import { createRoot } from 'react-dom/client'
import HelloMessage from './packs/hello_react'
import Calc from './packs/calc_react'
console.log(Calc)
const container = document.getElementById('hello')
const root = createRoot(container)
const container2 = document.getElementById('calc_result')
const root2 = createRoot(container2)
document.addEventListener('DOMContentLoaded', () => {
let el = (<div>
<Calc number={100} />
<Calc number={200} />
<Calc number={300} />
</div>);
root.render(<HelloMessage name="React" />)
root2.render(el);
})
import React from 'react'
import PropTypes from 'prop-types'
const Calc = props => {
let n = props.number;
let total = 0;
for (let i =0; i <= n; i++){
total += i;
}
return (<div>ゼロから {props.number} までの合計は、「{total}」です。</div>);
}
Calc.defaultProps = {
number: 0
}
Calc.propTypes = {
number: PropTypes.number
}
export default Calc
ここに行きつくまでに遭遇した問題を、以下に記録します。
PropTypesの問題
最初、ヘッダー以外何も表示されませんでした。
コンソールを確認してみると、以下のメッセージが出ています。
Webで PropTypesをググってみると、以下のページが有りました。
JS×Reactにおけるpropsの型定義を担うPropTypes入門 - Qiita
これを補助するものとして、型定義機能を提供してくれるのが
PropTypes
です
元々はReact本体に組み込まれていましたが、バージョン15.5よりprop-types
という別パッケージとして切り分けされました。
という事で、PropTypesを導入しました。
c:\Data\Web_study\Rails\RailsReactApp>yarn add prop-types yarn add v1.22.19 [1/4] Resolving packages... [2/4] Fetching packages... [3/4] Linking dependencies... [4/4] Building fresh packages... success Saved lockfile. success Saved 3 new dependencies. info Direct dependencies └─ prop-types@15.8.1 info All dependencies ├─ object-assign@4.1.1 ├─ prop-types@15.8.1 └─ react-is@16.13.1 Done in 3.87s.
そして、PropTypesを使用している「calc_react.js」に次の import文を追加しました。
import PropTypes from 'prop-types'
propsの型定義
画面に合計のメッセージが表示されるようになったのですが、コンソールには新たな warningメッセージが出ています。
そこで、先ほどの Webページで propsの型を確認してみると、「integer」というものが有りません。数字は、「number」になっています。
そこで、calc_react.js の以下の部分を number に変更しました。
number: PropTypes.integer ↓ number: PropTypes.number
props引き数の指定
ところが、また新しい warningメッセージが、コンソールに表示されています。
propの型がおかしいといわれています。
今度は値をセットする方の application.js を確認すると、「"100"」にしているために文字列として認識していると思われます。
そこで、以下のように「{100}」に変更しました。
<Calc number="100" /> <Calc number="200" /> <Calc number="300" /> ↓ <Calc number={100} /> <Calc number={200} /> <Calc number={300} />
これで、コンソールに error も warning も出ずに正常に表示されました。
データベースにアクセスする
Webpacker::Manifest::MissingEntryError
やはり、以下のように同じエラーが出ます。
前回はコーディングを変えて対応したのですが、今回はエラー・メッセージに対応して解決してみます。
エラー・メッセージの中で、「Shakapacker can't find dummy_data.js in ... manifest.json.」と言われています。また、「You have misconfigured Webpacker's 'config/webpacker.yml' file.」とも言われています。
また、エラーは index.html.erb の 1行目の、「<%= javascript_pack_tag 'dummy_data' %>」で発生しています。
Webで検索したところ、以下のようなページが有りました。
【Rails入門】Webpackerではじめるフロントエンド開発!Rails5.1対応 | 侍エンジニアブログ
【Rails】Webpackerの基本情報と実装方法 - AUTOVICE | 坂井光太郎のポートフォリオサイト
今一つ理解不足ではありますが、config/webpacker.ymlの設定が悪いのではないかと思い、確認してみました。
default: &default source_path: app/javascript source_entry_path: / nested_entries: false public_root_path: public public_output_path: packs cache_path: tmp/webpacker webpack_compile_output: true webpacker_precompile: true additional_paths: [] cache_manifest: false webpack_loader: 'babel' ensure_consistent_versioning: false compiler_strategy: digest ・・・
ちなみに、JavaScriptファイルの配置は以下のようになっています。
app/javascript: |-- packs:
| |-- dummy_data.js | └-- ・・・
└-- application.js
確かに、「source_path: app/javascript」と「source_entry_path: /」から、app/javascript/packs配下を見にいきそうにありません。
さらに、Webで以下の情報を見つけました。
Rails: Webpacker v5からShakapacker v6へのアップグレードガイド(翻訳)|TechRacho by BPS株式会社
5. source_entry_pathにネストしたディレクトリを置かないようにします。
エントリポイント用のファイルがsource_entry_pathのサブディレクトリに置かれていないかどうかを必ず確認してください。サブディレクトリ内のエントリポイント用ファイルは、shakacode/shakapacker v6ではサポートされていません。それらのファイルをトップレベルに移動し、それらのファイルのimportを調整してください。
そこで、app/javascript/packs配下の dummy_data.js を app/javascript配下に移動し、webpacker-dev-server を立ち上げ直すと、立ち上げ時のメッセージに、次の一行が追加されました。
asset js/dummy_data.js 2.79 KiB [emitted] (name: dummy_data) 1 related asset
RuntimeError
dummy_data.jsが見つからないというエラーは解消したようですが、今度は以下のようなエラーになりました。
エラー・メッセージの中で、「To prevent duplicated chunks on the page, you should call javascript_pack_tag only once on the page.」と言われています。
確かに、「javascript_pack_tag」は「application.html.erb」と「index_html.erb」の両方でコーディングしています。
Web上を調べてみましたが、ヒットするものは見つけられませんでした。
ただ、エラー・メッセージに「README.md」を見なさい、とあるので探しました。
「node_modules/shakapacker」のディレクトリーに在りました。
その中で、以下のような記述が有ります。
If you need configure your script pack names or stylesheet pack names from the view for a route or partials, then you will need some logic to ensure you call the helpers only once with multiple arguments. The new view helpers, `append_javascript_pack_tag` and `append_stylesheet_pack_tag` can solve this problem. The helper `append_javascript_pack_tag` will queue up script packs when the `javascript_pack_tag` is finally used.
google翻訳すると、
ルートまたはパーシャルのビューからスクリプト パック名またはスタイルシート パック名を構成する必要がある場合は、複数の引数を使用してヘルパーを 1 回だけ呼び出すようにするためのロジックが必要になります。 新しいビューヘルパー `append_javascript_pack_tag` と `append_stylesheet_pack_tag` は、この問題を解決できます。 ヘルパー `append_javascript_pack_tag` は、最終的に `javascript_pack_tag` が使用されると、スクリプト パックをキューに入れます。
という、判ったような判らない内容ですが、要は javascript_pack_tag を一つにしてくれるようです。
そこで、index.html.erb を以下のように変更しました。
<%= append_javascript_pack_tag 'dummy_data' %>
<h1 class="display-4 text-primary">Data#index</h1>
<div id="data"></div>
「http://localhost:3000/data/index」にアクセスすると、それらしい画面が表示されました。
ただ、画面上に「["dummy_data"]」が表示されているのと、コンソールに以下のWarningが出ています。
それぞれに対応したいと思います。
ReactDOM.render is no longer supported
まず基本的と思われる、「ReactDOM.render is no longer supported」のエラーに対応します。
次のWebページを参考に、dummy_data.js を変更します。
React18からはレンダリングにcreateRootを使おう
[変更前]
import React from "react" import ReactDOM from "react-dom" ・・・ document.addEventListener('DOMContentLoaded', () => { target_dom = document.querySelector('#data'); ・・・ function getData(f) { ・・・ ReacrDOM.render(el, target_dom); ・・・ ReactDOM.render(el, target_dom); ・・・ ReactDOM.render(el, target_dom); ・・・
[変更後]
import React from "react" import {createRoot} from "react-dom/client" ・・・ target_dom = document.querySelector('#data'); const root = createRoot(target_dom); ・・・ document.addEventListener('DOMContentLoaded', () => { ・・・ function getData(f) { ・・・ root.render(el); ・・・ root.render(el); ・・・ root.render(el); ・・・
Each child in a list should have a unique "key" prop
エラー・メッセージの中に参照ページが載っているので、そこを参照します。
キーは、どの項目が変更、追加、または削除されたかを React が識別するのに役立ちます。要素に安定したアイデンティティを与えるために、配列内の要素にキーを与える必要があります。
キーを選択する最良の方法は、兄弟間でリスト項目を一意に識別する文字列を使用することです。ほとんどの場合、データの ID をキーとして使用します。
上記Webページを参考に、dummy_data.js を変更します。
[変更前]
・・・ arr.push(<li class="list-group-item">{val.id}:{val.name} ({val.mail})</li>); ・・・
[変更後]
・・・ arr.push(<li class="list-group-item" key={val.id}>{val.id}:{val.name} ({val.mail})</li>); ・・・
Invalid DOM property `class`
これも、Webをググすると解決方法が有りました。
ReactのJSX構文内でclass属性を設定する場合 - 脳汁portal
これはclassという文字がReact側で予約後として他で使われているために発生するエラーです。
解決法もその後に書いてありますが、classNameを使えば解決します。
上記Webページを参考に、dummy_data.js を変更します。
[変更前]
・・・ arr.push(<li class="list-group-item">{val.id}:{val.name} ({val.mail})</li>); ・・・ const el = (<ul class="list-group">{arr}</ul>); ・・・
[変更後]
・・・ arr.push(<li className="list-group-item" key={val.id}>{val.id}:{val.name} ({val.mail})</li>); ・・・ const el = (<ul className="list-group">{arr}</ul>); ・・・
これで、一応 warningのメッセージはなくなりました。
{"dummy_data"] が表示される
これが一番厄介な問題です。
Webでググっても、解列方法がヒットしません。
そこで、application.html.erb の 「javascript_pack_tag」の行を削除し、index.html.erb の「append_javascript_pack_tag」を「javascript_pack_tag」に変更しました。
これにより、["dummy_data"] の表示は出なくなりました。
根本的な解決ではないので、また同じ問題が出てくると思われます。
正しくはソース(application.html.erbとindex.html.erb)の役割を明確にして、書き換えを行った方が良いと思っているので、その時に解決してみようと思います。
因みに、ページのソースを見ると、以下のように変化しています。
[変更前]
・・・ <script src="/packs/js/dummy_data.js" data-turbolinks-track="reload" defer="defer"></script> <script src="/packs/js/application.js" data-turbolinks-track="reload" defer="defer"></script> </head> <body class="container"> ["dummy_data"] ・・・
[変更後]
・・・ <body class="container"> <script src="/packs/js/runtime.js" defer="defer"></script> <script src="/packs/js/vendors-node_modules_webpack-dev-server_client_index_js_protocol_ws_3A_hostname_localhost_por-95065b.js" defer="defer"></script> <script src="/packs/js/vendors-node_modules_react-dom_client_js.js" defer="defer"></script> <script src="/packs/js/dummy_data.js" defer="defer"></script> ・・・
以上、Ruby on Rails を勉強していく中で、いろいろ経験することができました。
かなり長くなったので、続きがあれば別のページに載せていきたいと思います。