🦔

【React】複数の要素を順番に表示したい!

2023/07/30に公開
2

複数の要素をそれぞれ別の条件で 順番に 表示したいとき、
ありませんか?...ありますよね。私はありました。

ベターな解決方法

honey32さんによりよい方法を教えていただきました。
カスタムフックでさらにまとめて、返り値を限定すればよかったようです。。
ありがとうございました!

例えば

これは私が実際に遭遇した事例なのですが、以下のようにそれぞれ固有の条件でダイアログを表示したい、なんてことがあると思います。

  • セッションIDの期限が切れたので再ログインさせたい
  • 必要な設定が終わっていないので設定編集画面に遷移させたい
  • 最新のお知らせを通知したい、etc...

そんなとき、以下のように実装してしまうと。。。

const DialogWrapper = () => {
  return (
    <>
      <DialogA />
      <DialogB />
      <DialogC />
    </>
  )
}

const DialogA = ({ children }) => {
  const { openA } = useDialogA();
  return <Dialog open={openA} />
}

const DialogB = ({ children }) => {
  const { openB } = useDialogB();
  return <Dialog open={openB} />
}

const DialogC = () => {
  const { openC } = useDialogB();
  return <Dialog open={openC} />
}

困った!

なんと、
条件に当てはまったダイアログがすべて表示されてしまう
ではありませんか!

そうなんです。当然ではありますが、ダイアログを複数用意してそれぞれで表示非表示を切り替えていると、条件に当てはまるダイアログがすべて表示されてしまいます。
優先度が高いものから順番に、ひとつずつダイアログを表示したい。。。

※ そもそも、ダイアログをひとつだけにして、中身のコンテンツを順番に切り替えていく方法でも可能だと思いますが、条件が増えたときに混沌が訪れると考え、あえてダイアログを分けて実装しています。
また、すべてのダイアログの親要素にて表示するダイアログを管理しようかとも考えましたが、実装も管理も大変そうなので簡単な実装方法を探しました。

結論

入れ子とchildrenプロパティを使った条件分岐で実装できました。

const DialogWrapper = () => {
  return (
    <DialogA>
      <DialogB>
        <DialogC />
      </DialogB>
    </DialogA>
  )
}

const DialogA = ({ children }) => {
  const { openA } = useDialogA();
  
  if (!openA) return children;
  return <Dialog open={openA} />
}

const DialogB = ({ children }) => {
  const { openB } = useDialogB();
  
  if (!openB) return children;
  return <Dialog open={openB} />
}

const DialogC = () => {
  const { openC } = useDialogB();
  return <Dialog open={openC} />
}

コードのとおりですが、DialogA → DialogB → DialogC の順番で表示されます。

意図した通り動かなかった実装

各コンポーネントでnullをリターンし、親で条件分岐をしようとしました。
返り値がnullではなくオブジェクトになってしまって、意図したとおりには動作しませんでした。

const DialogWrapper = () => {
  return <DialogA /> || <DialogB /> || <DialogC />;
}

const DialogA = () => {
  const { openA } = useDialogA();
  
  if (!openA) return null;
  return <Dialog open={openA} />
}

const DialogB = () => {
  const { openB } = useDialogB();
  
  if (!openB) return null;
  return <Dialog open={openB} />
}

const DialogC = () => {
  const { openC } = useDialogB();
  return <Dialog open={openC} />
}

まとめ

  • コンポーネントでreturn nullをしても、返り値はnullにはならない
  • 入れ子とchildrenプロパティを駆使して表示するコンテンツを切り替えられる

ということを学びました。

もっとベターな方法があったり、他にも実現できる方法などありましたらご指摘、ご教授いただけますと、大変嬉しいです!!
ありがとうございました~

Discussion

Honey32Honey32

以下のように書けば、表示される条件を明示的に書けると思います。

const DialogContainer = () => {
  const dialogState = useExclusiveDialogState();

  return (
    <>
      <Dialog open={dialogState === "a"} />
      <Dialog open={dialogState === "b"} />
      <Dialog open={dialogState === "c"} />
    </>
  )
}

// 各ダイアログの開閉状態を、排他的に制御する
const useExclusiveDialogState = () => {
  const { openA } = useDialogA();
  const { openB } = useDialogB();
  const { openC } = useDialogC();

  if (openA) return "a";
  if (openB) return "b";
  if (openC) return "c";

  return undefined;
}
たかだたかだ

コメントありがとうございます!
お返事が遅くなり申し訳ありません。

カスタムフックでまとめて返り値をひとつに限定すればよかったのですね。
記事に記述を追加させていただきました。
大変勉強になりました、ありがとうございました!