複雑なステート(ショップの例)
ここではReactを使います。ステートをReactに持たせます。
デモはこちらに用意しています。
useStateで作成されたステートを更新しますReactはステートを中心とした情報の流れを強制しています。
イベントハンドラ ==> ステート ==> 再レンダリングと情報が流れます。
<!DOCTYPE html> <html> <!-- ... --> <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> <%= javascript_include_tag "application", "data-turbo-track": "reload", type: "module" %> <%= javascript_include_tag "react_iphone", "data-turbo-track": "reload", type: "module" %> </head> <body> <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> </body> <% 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 %> </html>
javascript_include_tag "react_iphone"でReactで書かれたコードを読み込みます。後述しますが、Reactは<div id="root"></div>の箇所に挿入されます<script type="application/json" id="catalog-data">で行います。@catalog_dataとしてコントローラから渡されたデータを、この中にJSON形式で書き込みます// ... 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オブジェクトでロジックを処理して、コンポーネントを再レンダリングしています