🍎

Reactで配列データが空のときに非表示のベストプラクティスを探している

2022/09/07に公開

要件1

次のような感じで空かもしれない配列を受け取ってリストを表示するコンポーネントがあるとします。
0件のときは要素自体をレンダリングしたくないします。
またレイアウトに関する責務を基本的は負いたくないとします。

components/ItemList.tsx
export default ({ items }: { items: string[] }) => {
  if (items.length === 0) return null
  
  return (<ul>
    { items.map((item) => (
        <li key={item}>
	  {item}
	</li>
      )
    }
  </ul>)
}

要件2

ItemListを利用する側のコンポーネントはレイアウトを行い、用意したデータをItemListコンポーネントに渡します。

index.module.css
.main {
  display: flex;
  flex-direction: columns;
  gap: 64px;
}

.section {
  width: 100%;
  max-width: 960px;
  padding-left: 56px;
  padding-right: 56px;
}

pages/index.tsx
import ItemList from 'components/ItemList'
import indexStyles from 'index.module.css'

export default () => {
  const items = ['要素1', '要素2', '要素3', '要素4']
  
  return (
    <main className={indexStyles.main}>
      <header className={indexStyles.section}>
        <h1>見出し</h1>
      </header>
      <section className={indexStyles.section}>
        カバーエリア
      </section>
      <section className={indexStyles.section}>
        <ItemList items={items} />
      </section>
      <footer className={indexStyles.section}>
	© Hoge
      </footer>
    </main>
  )
}

課題

このとき、 items = [] の場合のレンダリング結果は次のようになります。
空のsectionが出来るため不自然な余白が発生します。これを解決したい。

<main class="main">
  <header class="section">
    <h1>見出し</h1>
  </header>
  <section class="section">
    カバーエリア
  </section class="section">
  <section class="section"></section>
  <footer class="section">
    © Hoge
  </footer>
</main>

解決案1

sectionの有無は利用側の責務なので利用側でも分岐を入れてレンダリングをコントロールする案。
でもこれだとちょっと冗長だなーと思っています。

pages/index.tsx(部分)
      {(items.length > 0 && (
        <section className={indexStyles.section}>
          <ItemList items={items} />
        </section>
      )}

解決案2

ItemList にレイアウトのロジック自体は持たせないものの、レイアウト情報を横流しできるようにする案。
これはこれで単純なものはいいのですが、ItemList内部でルート要素に表示ロジックとの干渉を意識する必要があったり心理的なコストが高い印象です。

components/ItemList.tsx
export default ({ items, className }: { items: string[], className?: string }) => {
  if (items.length === 0) return null
  
  return (<ul className={className}>
    { items.map((item) => (
        <li key={item}>
	  {item}
	</li>
      )
    }
  </ul>)
}
pages/index.tsx(部分)
      <ItemList items={items} className={indexStyles.section} />

暫定まとめ

.sectionに何をさせるかという設計によるとは思いつつ、基本的にはセマンティクスを意識すれば 解決法1 だと思っています。

あるいは、 配列データの有無によってレンダリングしないコンポーネント という明示的なコンポーネントを用意しそれを踏まえた設計にするのも良いのかもしれません。

<ConditionalComponent exists={items}>
  <section className={indexStyles.section}>
    <ItemList items={items} />
  </section>
</ConditionalComponent>

Discussion