React基礎講座 #7 条件付きレンダリング

読了 7分

前回はイベント処理を扱いながら最後に{lastSubmitted && ...}のような表現をちらっと見ました。今回は画面を状態に応じて違って描く条件付きレンダリング(Conditional Rendering)パターンをまとめてみます。

条件付きレンダリングとは #

ログインの可否によって異なるメニューを見せたり、データを取得する間ローディング表示を出したり、入力検証に失敗したときエラーメッセージを見せたりすることはすべてのアプリで起こります。このようにある条件に応じて異なるJSXをレンダリングすることを条件付きレンダリングと呼びます。

Reactでは別の構文があるわけではなく、JavaScriptの条件表現をそのまま活用します。もっともよく使われる4つのパターンを見ていきます。

パターン1. if文で分岐 — early return #

もっとも直感的な方法は、コンポーネント関数内でifで分岐して異なるJSXを返すことです。

src/Greeting.jsx
function Greeting({ user }) {
  if (!user) {
    return <p>ログインが必要です</p>;
  }

  return <h1>こんにちは{user.name}さん!</h1>;
}

export default Greeting;

userがなければログイン案内を返して関数が終わります。2番目のreturnuserがあるときだけ実行されます。このように関数を早く抜け出す方式をearly returnと呼び、分岐が大きいときに可読性がよいです。

パターン2. 三項演算子 — JSXの中で2つの分岐 #

JSXの真ん中で2つのうち1つを選択しなければならないなら、JavaScriptの三項演算子(条件 ? A : B)を使います。

src/LoginButton.jsx
function LoginButton({ isLoggedIn }) {
  return (
    <button>
      {isLoggedIn ? 'ログアウト' : 'ログイン'}
    </button>
  );
}

export default LoginButton;

JSXの波括弧の中にはしか入らないという点を覚えていますか?if文は文(statement)なので入れられませんが、三項演算子は式なので可能です。

JSX自体を2つに分けるのにも使えます。

src/UserStatus.jsx
function UserStatus({ user }) {
  return (
    <div>
      {user ? (
        <p>こんにちは{user.name}さん!</p>
      ) : (
        <p>ログインが必要です</p>
      )}
    </div>
  );
}

ただし三項が長くなると可読性が落ちるので、JSXの塊が大きければパターン1(early return)や変数に分離する方がよいです。

パターン3. &&演算子 — 見えるか見えないか #

「条件が真のときだけ何かを見せて、偽なら何も見せたくない」というケースがもっとも多いです。このとき&&演算子がよく合います。

src/Notification.jsx
function Notification({ unreadCount }) {
  return (
    <div>
      <h2>通知</h2>
      {unreadCount > 0 && (
        <p>未読のメッセージが{unreadCount}件あります</p>
      )}
    </div>
  );
}

export default Notification;

A && BはJavaScriptでAが真ならBを返し、偽ならAを返します。unreadCount > 0が真なら<p>...</p>がそこに入り、偽ならfalseが入ります。Reactはfalsenullundefinedを画面に何も描画しないので、結果的に消えたように見えます。

&&使用時の落とし穴 — 数字の0 #

&&のよくある落とし穴が1つあります。次のコードを見てください。

問題のあるコード
function Cart({ count }) {
  return (
    <div>
      {count && <p>カートに{count}個入っています</p>}
    </div>
  );
}

countが0のとき、意図は「何も見えないこと」ですが、実際には画面に0という数字がそのまま出力されます。0 && X0を返すからで、Reactは数字0は画面に本当に描画します(falsenullundefinedだけ描画しません)。

解決策は明示的にブーリアンに変えることです。

修正したコード
{count > 0 && <p>カートに{count}個入っています</p>}

count > 0は常にtrueまたはfalseなので安全です。&&の左側に明確なブーリアンを置く習慣をつけると、この落とし穴に陥りません。

パターン4. nullを返す — コンポーネント自体を描画しない #

条件が満たされなければコンポーネント全体が画面に現れない必要があるときはnullを返します。

src/Banner.jsx
function Banner({ message }) {
  if (!message) return null;

  return (
    <div style={{ background: '#fffbcc', padding: '12px' }}>
      {message}
    </div>
  );
}

export default Banner;

nullを返すとReactはそのコンポーネントの位置に何も描画しません。親の立場では<Banner message={...} />と常に書いておけばよく、見せるか見せないかはBanner本人が決める構造です。すっきりしていますね。

変数にJSXを入れておく #

分岐が複雑になればJSXを変数に入れておいて、その変数を使う方式が可読性によいです。

src/Page.jsx
function Page({ status, data, error }) {
  let content;

  if (status === 'loading') {
    content = <p>読み込み中...</p>;
  } else if (status === 'error') {
    content = <p style={{ color: 'red' }}>エラー: {error}</p>;
  } else {
    content = <p>データ: {data}</p>;
  }

  return (
    <div>
      <h1>ページタイトル</h1>
      {content}
    </div>
  );
}

JSXはただのJavaScriptの値なので、変数に入れたり、関数から返されたり、オブジェクトに入れておくこともできます。

パターン整理 #

状況推奨パターン
分岐結果がコンポーネント全体のJSXである場合early return (if)
2つのうち1つを見せたい場合三項演算子 (A ? B : C)
条件が真のときだけ見せたい場合&&演算子 (左側はブーリアンで)
コンポーネントがまったく見えてはならない場合return null
分岐が3つ以上または複雑な場合変数にJSXを入れて使用

Reactだけの特別な構文があるのではなく、JavaScriptの条件表現をJSXの中でそのまま使うという点が核心です。

自分でやってみる #

前回作ったMessageFormを少し拡張してみます。「名前を入力しないと警告表示」「メッセージを入力しないと警告表示」「両方とも正常なら送信可能」になるようにです。

src/MessageForm.jsx
import { useState } from 'react';

function MessageForm() {
  const [name, setName] = useState('');
  const [message, setMessage] = useState('');
  const [lastSubmitted, setLastSubmitted] = useState(null);

  const isValid = name.length > 0 && message.length > 0;

  function handleSubmit(e) {
    e.preventDefault();
    if (!isValid) return;
    setLastSubmitted({ name, message });
    setName('');
    setMessage('');
  }

  return (
    <div style={{ padding: '16px', border: '1px solid #ccc', borderRadius: '8px' }}>
      <form onSubmit={handleSubmit}>
        <div>
          <input
            type="text"
            placeholder="名前"
            value={name}
            onChange={(e) => setName(e.target.value)}
          />
          {name.length === 0 && (
            <span style={{ color: 'red', marginLeft: '8px' }}>名前を入力してください</span>
          )}
        </div>
        <div style={{ marginTop: '8px' }}>
          <input
            type="text"
            placeholder="メッセージ"
            value={message}
            onChange={(e) => setMessage(e.target.value)}
          />
          {message.length === 0 && (
            <span style={{ color: 'red', marginLeft: '8px' }}>メッセージを入力してください</span>
          )}
        </div>
        <button type="submit" disabled={!isValid} style={{ marginTop: '8px' }}>
          {isValid ? '追加' : '入力を完了してください'}
        </button>
      </form>
      {lastSubmitted ? (
        <p style={{ marginTop: '12px' }}>
          最後の入力: <strong>{lastSubmitted.name}</strong>  {lastSubmitted.message}
        </p>
      ) : (
        <p style={{ marginTop: '12px', color: '#888' }}>
          まだ送信されたメッセージがありません
        </p>
      )}
    </div>
  );
}

export default MessageForm;

ここで使った条件付きレンダリングパターン:

  • name.length === 0 && <span>...</span>&&で入力がされていないときだけ警告表示
  • disabled={!isValid} — 有効性に応じてボタンの活性/非活性
  • isValid ? '追加' : '入力を完了してください' — 三項でボタンテキストの分岐
  • lastSubmitted ? <p>...</p> : <p>...</p> — 三項で案内メッセージの分岐

複数のパターンが1つの画面に自然に混じって使われる例です。直接入力してみながら画面がどう反応するか観察してみてください。

おわりに #

今回の記事では条件付きレンダリングの4つのパターン(early return、三項、&&nullを返す)と、変数にJSXを入れることを見てきました。どれか1つが正解なのではなく、状況に応じてもっとも可読性のよいパターンを選ぶ感覚が重要です。そして&&使用時は数字0の落とし穴さえうまく避ければよいです。

これまでは画面に見せるデータが1〜2個に制限的でした。しかし実際のアプリでは投稿リスト、商品リスト、通知リストのように複数のデータを一度に描画しなければなりません。次の記事「React基礎講座 #8 リストとkey」では、配列を画面に描画する方法と、その際に必ず登場するkeyという特別なpropの意味を扱ってみます。

X