😊

【React/Next.js】その条件分岐、本当に大丈夫? 0 が表示される意外な罠と解決策

に公開

React や Next.js で開発を進めていると、条件付きレンダリングは日常的に使う機能の一つです。しかし、一見正しく見える条件分岐が、思わぬ挙動を引き起こすことがあります。今回は、特に && 演算子を使った条件付きレンダリングで、数値の 0 が意図せず画面に表示されてしまうという「バグ」に遭遇した事例と、その原因、そして確実な対策について深掘りしていきます。
きっかけは「ポイント未使用なのに 0 が表示される」バグ
ある予約システムの完了画面で、顧客がポイントを使用しなかった場合(使用ポイントが 0 の場合)でも、なぜか画面に 0 という数字だけが表示されてしまう、という奇妙な現象に遭遇しました。
問題のコードは以下のような JSX でした。

// 問題があったコードの簡略例
{
  reservation.usePoints && reservation.usePoints > 0 && (
    <>
      <Separator className="my-2" />
      <div className="flex justify-between items-center mt-2">
        <span className="text-sm text-active">ポイント使用</span>
        <span className="font-medium text-active">
          - ¥{reservation.usePoints.toLocaleString()}
        </span>
      </div>
    </>
  )
}

reservation.usePoints は顧客が使用したポイント数を格納するプロパティで、数値型です。ポイントを使用していない場合は 0 が入ります。
このコードの意図は、「使用ポイントが存在し、かつ0より大きい場合のみ、ポイント使用の詳細を表示する」というものです。
当初、reservation.usePoints が 0 の場合、最初の reservation.usePoints (値が 0) が JavaScript の評価で falsy となるため、&& 演算子はその時点で評価を中断し、右側のJSXはレンダリングされないと期待していました。実際、デバッグコンソールで条件式 typeof reservation.usePoints === 'number' && reservation.usePoints > 0 を評価すると、正しく false が返ってきていました。
しかし、ブラウザの開発者ツールでDOMを確認すると、驚くべきことに、ポイント表示セクションが来るべき場所に、テキストノードとして 0 だけが出力されていたのです。
Apply to page.tsx
}
Separator コンポーネントや div といった構造は一切レンダリングされず、ただ 0 という文字だけが孤立して存在していました。
なぜ 0 が表示されたのか?JavaScript の Truthy/Falsy と React の JSX レンダリングの挙動
この不可解な現象の鍵は、JavaScriptにおける 0 の評価と、ReactがJSXをどのように扱うかという点にあります。
JavaScriptでは、0 は falsy な値として扱われます。if (0) や 0 && anything といった式は、期待通り false または 0 と評価され、anything の部分は実行されません。
ReactのJSXにおける && 演算子を用いた条件付きレンダリングは、通常、左辺が falsy な場合、右辺のコンポーネントはレンダリングされません。null、undefined、false が条件式の結果としてJSX内に記述された場合、Reactはそれらを無視し、何もDOMに出力しません。
では、なぜ今回は 0 が表示されたのでしょうか?
Reactのドキュメントやコミュニティの議論を深掘りすると、数値の 0 は、false、null、undefined とは異なり、Reactによって「有効な子要素ではないが、無視もされない」という特殊な扱いを受けることがある、ということが分かってきました。特に && 演算子の左辺が数値の 0 となった場合、Reactはその 0 をテキストノードとしてDOMにレンダリングしてしまうことがあるのです。
今回のケースでは、reservation.usePoints が 0 だったため、reservation.usePoints && ... の最初の部分が 0 と評価され、その 0 がそのままDOMに出力されてしまった、と考えられます。その後の reservation.usePoints > 0 の評価や、<>...</> の部分は、最初の 0 (falsy) によって短絡評価され、実行されていませんでした。だからこそ、構造体は出力されず、0 だけが残ったのです。
確実な対策:条件式の結果を boolean または null にする
この問題を回避し、意図した通りに条件付きレンダリングを行うための最も確実な方法は、JSXに渡す条件式の結果が、必ず boolean 値(true または false)になるようにするか、あるいは false の場合に明示的に null を返すようにすることです。

  1. 三項演算子 (? :) を使用する

     {typeof reservation.usePoints === 'number' && reservation.usePoints > 0 ? (
       <>
         <Separator className="my-2" />
         <div className="flex justify-between items-center mt-2">
           <span className="text-sm text-active">ポイント使用</span>
           <span className="font-medium text-active">
             - ¥{reservation.usePoints.toLocaleString()}
           </span>
         </div>
       </>
     ) : null}
    

この方法では、条件が false の場合に明示的に null を返します。Reactは null をレンダリングしないため、意図しない 0 が表示されることはありません。これは最も推奨される明確な解決策です。
2. 条件を Boolean() または !! でキャストする

{Boolean(typeof reservation.usePoints === 'number' && reservation.usePoints > 0) && (
  <>
    {/* JSX content */}
  </>
)}

このように、条件式全体を Boolean() で囲むか、二重否定演算子 !! を使うことで、結果を明示的に true または false に変換します。false && <Component /> となれば、Reactは正しくコンポーネントをレンダリングしません。
まとめ:見落としがちな条件分岐の挙動に注意
React (Next.js) における && を用いた条件付きレンダリングは非常に便利ですが、今回のように数値の 0 が関わる場合に予期せぬ挙動を示すことがあります。これはReactのバグというよりは、JSXの仕様とJavaScriptの型評価の組み合わせによる、ややトリッキーな挙動と言えるでしょう。
デバッグの際には、コンソールでの条件評価だけでなく、実際にDOMがどのように出力されているかを確認することが重要です。そして、条件付きレンダリングを行う際には、三項演算子を使って false の場合に明示的に null を返すか、条件式を Boolean にキャストすることで、より堅牢で予測可能なコードを書くことを心がけましょう。

Discussion