Apple Store模写(複雑なステート)
ここではReactを使います。ステートをReactに持たせます。
デモはこちらに用意しています。
useState
で作成されたステートを更新しますReactはステートを中心とした情報の流れを強制しています。イベントハンドラ ==> ステート ==> 再レンダリングと情報が流れます。
<%= provide :head, javascript_include_tag("react_iphone", "data-turbo-track": "reload", type: "module") %> <div class="container container-lg mx-auto px-4 pt-16"> <div class="mx-auto min-w-[1028px] lg:max-w-5xl"> <div id="root"></div> </div> </div> <% if @catalog_data %> <script type="application/json" id="catalog-data"> <% @catalog_data[:images].transform_values! { image_path(_1) } %> <%= @catalog_data.to_json.html_safe %> </script> <% end %>
javascript_include_tag "react_iphone"
でReactで書かれたコードを読み込みます。後述しますが、Reactは<div id="root"></div>
の箇所に挿入されます<script type="application/json" id="catalog-data">
で行います。@catalog_data
としてコントローラから渡されたデータを、この中にJSON形式で書き込みます
image_path()
で@catalog_data
を処理しているところは、Railsがアセットのフィンガープリント(ダイジェスト)を追加するためです// ... document.addEventListener("turbo:load", () => { const dataJSON = document.getElementById('catalog-data').textContent const data = JSON.parse(dataJSON) const root = createRoot(document.getElementById("root")) root.render(<IPhoneShow catalogData={data}/>); });
turbo:load
のイベントを待ちます。HotwireはSPAなので、DOMContentLoaded
が発火するとは限りません。Turboのページ遷移の時に発火するturbo:load
を使う方が無難です#catalog-data
要素のデータを読み込み、JSONをparseして、propsとしてIPhoneShow
コンポーネントに渡しています。これはオプションごとの価格情報などを含むデータですroot.render()
でReactコンポーネントの初回レンダリングをしていますimport React, {useState} from "react" import IPhone from "./models/IPhone" import IphoneOption from "./react/components/IphoneOption" import IphoneColorOption from "./react/components/IphoneColorOption" export function IPhoneShow({catalogData}) { const [iPhoneState, setIphoneState] = useState( {model: null, color: null, ram: null} ) const [colorText, setColorText] = useState("Color – Natural Titanium") const iPhone = new IPhone(iPhoneState, catalogData) function handleOptionChange(name, value) { setIphoneState({...iPhoneState, [name]: value}) } function handleColorChange(color) { setIphoneState({...iPhoneState, color}) } function handleSetColorText(selectedColor) { setColorText(catalogData.colors[selectedColor].full_name) } function handleResetColorText() { setColorText(iPhone.fullColorName()) } function itemPricing(model, ram) { const pricing = iPhone.pricingFor(model, ram) return [`From \$${pricing.lump.toFixed(2)}`, `or \$${pricing.monthly.toFixed(2)}/mo.`, "for 24 mo."] } return (<> ... </> ) }
iPhoneState
のステートに保持しますcolorText
のステートに保持します。これはホバー時に表示するだけの内容なので、製品オプションのステートとは別に保持しますIphone
クラスのインスタンスを作成します。これはStimulusで使用したモデルをそのまま再利用していますhandleOptionChange
, handleColorChange
の関数はオプション選択イベントを処理するイベントハンドラです。iPhoneState
を更新しますhandleResetColorText
はホバー時のカラーテキストを更新するものですiphoneState
ステートに保存し、IPhoneオブジェクトでロジックを処理して、コンポーネントを再レンダリングしています#render*
のメソッドが不要になります#render*
メソッドを使ってステートをDOMに反映させました。一方でReactの場合はIphoneShow
コンポーネント(app/javascript/react/components/IPhoneShow.jsx
)のJSXの中にロジックが埋め込まれています(コード量が多くなるのでここでは掲載していません。GitHub上で確認してください)