🧐
モーダルの出し分け方法を色々考えてみる
はじめに
モーダルを作成する機会があったのですが、モーダル内コンテンツの出し分けにかなりハマりました。
半分メモ書きみたいになってますが一応備忘録として残しておきます。
なお出し分ける部分だけに着目しているため、CSSやストアでの状態管理・アクセシビリティについては全く考慮しておりません。
やりたいこと
Modal01のボタンを押したらModal01コンポーネントのコンテンツが表示される
Modal02のボタンを押したらModal02コンポーネントのコンテンツが表示される
結論
色々試した結果、結局これが一番シンプルに書けそうでした。
ただ引数でコンポーネントを渡すのがなんとも気持ち悪かったので、他の方法でできないか色々試してみました。
function App() {
const [isOpen, setIsOpen] = useState(false);
const [openModalComp, setOpenModalComp] = useState<JSX.Element>();
const open = (props: JSX.Element): void => {
setIsOpen(true);
setOpenModalComp(props);
};
return (
<>
<div>
<button onClick={() => open(<Modal01 />)}>Modal01</button>
<button onClick={() => open(<Modal02 />)}>Modal02</button>
</div>
{isOpen && <Modal>{openModalComp}</Modal>}
</>
);
}
試したけどうまくいかなかった方法
押したボタンのテキストを取得し、createElement()で表示する
buttonのテキストを取得し、そのテキストをcreateElement()の第一引数(タグ名)として使えないかと思った
**
なぜうまくいかなかったか
- ボタンが複数あった場合にテキストの取得方法がわからない
const App = () => {
const [isOpen, setIsOpen] = useState(false);
const [openModalComp, setOpenModalComp] = useState("Modal01");
const open = (): void => {
setIsOpen(true);
};
return (
<>
<div>
<button onClick={() => open()}>{openModalComp}</button>
{/* Modal02のテキストどう取得する? */}
<button onClick={() => open()}>Modal02</button>
</div>
{isOpen && createElement(Modal01, null, "モーダル1です")}
</>
);
};
- createElement()の第一引数には関数そのものを入れなければならず、stringや関数の戻り値を入れることはできないっぽい(useStateを挟むと関数が実行されてしまい、戻り値が入ってしまう)
const App = () => {
const [isOpen, setIsOpen] = useState(false);
const [openModalComp, setOpenModalComp] = useState();
const open = (component): void => {
setIsOpen(true);
setOpenModalComp(component);
};
return (
<>
<div>
<button onClick={() => open(Modal01)}>Modal01</button>
<button onClick={() => open(Modal02)}>Modal02</button>
</div>
{/* これは正常に表示される */}
{isOpen && createElement(Modal01, null, "モーダル1です")}
{/* これはエラーになる */}
{isOpen && createElement(openModalComp, null, "モーダル1です")}
</>
);
};
↓これでもダメでした
const App = () => {
const [isOpen, setIsOpen] = useState(false);
const [openModalComp, setOpenModalComp] = useState("");
const comp = {
Modal01: Modal01,
Modal02: Modal02,
};
const open = (component: string): void => {
setIsOpen(true);
setOpenModalComp(comp[component]);
};
return (
<>
<div>
<button onClick={() => open("Modal01")}>Modal01</button>
<button onClick={() => open("Modal02")}>Modal02</button>
</div>
{isOpen && createElement(openModalComp, null, "hello")}
</>
);
};
console.log(openModalComp)の結果
関数が実行され戻り値が返されており、createElementで使えない
console.log(Modal01)の結果
関数は実行されず関数そのものが返され、createElementで使える
asプロパティでタグ名を書き換える
以下を参考にasプロパティでタグ名を書き換えようと思った
なぜうまくいかなかったか
-
asはdivやpなどの既定のタグのみしか受け付けないっぽく、コンポーネント名を入れることができなかった
-
↓この方法でもやはりpropsで渡される値がuseStateを通っているとエラーになる
interface Props2 {
tag?: React.ElementType;
children?: string;
}
const Main2 = (props: JSX.Element) => (
<>
<Example2 tag={props.props}></Example2>
</>
);
export const Example2 = ({ tag: Tag = "div", children }: Props2) => (
<Tag>{children}</Tag>
);
コメントお待ちしてます
初学者かつメモ書き状態のためわかりにくいところ多々あるかと思いますが、もしここが違っている、こうすればできるなどあればコメントいただけるとありがたいです。
参考文献
Discussion
失礼します。
ステートは Single Source of Truth (信頼できるただひとつの情報源) として扱えるので一つにまとめられますが、(いや、でも面倒くさいなら
isModal01Open
isModal02Modal
の2つのステートに分けるだけで十分です)modalState に基づいて JSX をレンダリングする場では「共通化をしない」ほうが便利です。Modal01, 02 などの仕様がいくら複雑になっても対応できます。
コメントありがとうございます、本当にありがたいです!試してみます!