アプリに機能を追加したとたん、コンパイルは通るのに画面が真っ赤になります。そのエラーメッセージが「Invariant Violation: text strings must be rendered within a <Text> component」です。
このエラー、初めて見たときはかなり戸惑いますよね。どこで文字列が出てしまっているのか見当もつかないことがあります。私自身、何度かこれで詰まってきた経験があるので、実際によく起きるパターンと対処法をまとめておきます。
なぜこのエラーが起きるのか
HTML なら <div> の中に文字列を直接書いても問題ありません。ブラウザが勝手に解釈してくれるからです。でも React Native は話が違います。
React Native の View は、テキストを直接レンダリングする仕組みを持っていません。文字列を表示するには、必ず <Text> コンポーネントでラップする必要があります。これは React Native の根本的な制約で、Rork で生成されたコードも例外ではありません。
// ❌ これは HTML では動くが React Native ではエラーになる
<View>
エラーメッセージ
</View>
// ✅ React Native ではこうする
<View>
<Text>エラーメッセージ</Text>
</View>Rork の AI が生成するコードは基本的にこのルールを守ってくれますが、手動で修正を加えたときや、条件分岐を追加したときに誤って文字列が <Text> の外に出てしまうことがあります。
パターン1:&& 演算子で数値が 0 のままレンダリングされる
これが最も多い原因です。GSC のデータを見ていても、このパターンに関連した検索が多く届いています。
// ❌ items.length が 0 のとき "0" という文字列が画面に表示されてしまう
{items.length && <ItemList items={items} />}JavaScript では 0 && <何か> は 0 を返します。数値の 0 は React Native にとって「Text コンポーネントの外にある文字列」と見なされるため、このエラーが発生します。
修正方法は2つあります。
// ✅ 方法1: Boolean() で明示的に変換する
{Boolean(items.length) && <ItemList items={items} />}
// ✅ 方法2: 比較演算子を使う(こちらのほうが読みやすくておすすめです)
{items.length > 0 && <ItemList items={items} />}
// ✅ 方法3: 三項演算子にする
{items.length > 0 ? <ItemList items={items} /> : null}個人的には方法2が一番わかりやすいと思います。「0より大きい場合」と読めるので、コードレビューしてもらうときにも意図が伝わりやすいです。
同じ問題は undefined や null でも起きることがありますが、false は React Native でレンダリングされないので問題ありません。数値型のゼロだけが特殊、と覚えておくと良いでしょう。
パターン2:条件分岐でテキスト文字列が View の直下に入り込む
Rork が生成したコードに手を加えるとき、つい <Text> を外してしまうことがあります。
// ❌ エラーになる
<View>
{isLoading ? "読み込み中..." : <ContentView />}
</View>
// ✅ テキスト部分を <Text> でくるむ
<View>
{isLoading ? <Text>読み込み中...</Text> : <ContentView />}
</View>また、テンプレートリテラルを使っていて気づきにくいケースもあります。
// ❌ エラーになる(テンプレートリテラルも文字列)
<View>
{`現在 ${count} 件のアイテムがあります`}
</View>
// ✅ <Text> でくるむ
<View>
<Text>{`現在 ${count} 件のアイテムがあります`}</Text>
</View>エラーメッセージが指しているのは「文字列を直接置いている場所」なのですが、React Native のスタックトレースは少し読みにくいことがあります。「最後に触った部分のコンポーネント」を重点的に確認するのが早道です。
パターン3:Style 配列の書き方ミスによる混入
エラーメッセージに「style array boolean」のキーワードが含まれる検索がある理由はここにあります。スタイルを条件付きで適用しようとして、意図せずコンポーネントツリーの構造を壊してしまうパターンです。
// ❌ これは間違いではないが、前後のコードとの組み合わせで問題が起きることがある
<Text style={[styles.base, isActive && styles.active]}>
テキスト
</Text>実はこの書き方自体は正しく、isActive が false のとき styles.active の代わりに false が配列に入りますが、React Native はスタイル配列の中の false を無視してくれます。
問題が起きるのは、このパターンをJSX の要素として使ってしまうときです。
// ❌ エラーになる(スタイルではなく要素の選択で && を使うとき)
<View>
{isActive && "アクティブです"} // 数値でないのでこれは動く場合もあるが
{score && "スコア: " + score} // score が 0 だとアウト
</View>
// ✅ 安全な書き方
<View>
{isActive && <Text>アクティブです</Text>}
{score > 0 && <Text>スコア: {score}</Text>}
</View>スタイルの条件分岐と要素の条件分岐を混同しないようにすることが大切です。
パターン4:改行・スペースの混入(意外と気づきにくい)
JSX の中に半角スペースや改行が文字列として残ってしまうケースです。エディターで整形したときに発生しやすいです。
// ❌ コンポーネントタグの間に改行があるとエラーになることがある
<View>
{" "}
<Button title="送信" />
</View>
// ✅ 空のスペースが必要なら <Text> でくるむか削除する
<View>
<Button title="送信" />
</View>Rork の AI が生成したコードではあまり起きませんが、コードを手動で整理したり、別のファイルからコピーペーストしたりしたときに紛れ込みやすいパターンです。
VSCode を使っていれば、「エラーが出た箇所の前後をすべて選択して一度削除し、再入力する」という方法が意外と効きます。
エラーの原因を素早く特定するコツ
このエラーが出たとき、スタックトレースに「どのコンポーネントか」は出ますが、「どの行か」がわかりにくいことがあります。私がよく使うデバッグ手順はこうです。
1. 最後に変更したコンポーネントを最初に疑う
エラーは変更を加えた直後に出ることがほとんどです。Git の差分を見れば原因箇所が絞り込めます。
2. コンポーネントの JSX を折りたたんでいく
return の中を少しずつコメントアウトして、どの部分でエラーが消えるかを確認します。二分探索的なアプローチが有効です。
3. 型チェックを活用する
TypeScript を使っている場合、JSX.Element | string のような型が混入していないかを確認します。string 型が JSX の中に直接入っていれば、それが原因の可能性が高いです。
関連する React Native のエラーについては React Native Expo のランタイムエラー対処法 もあわせて参考にしてみてください。状態管理に起因するレンダリングの問題は state が更新されない・再レンダリングされない問題の直し方 で詳しく解説しています。
「直したのにまだ出る」ときのチェックリスト
修正したつもりなのにエラーが消えないときは、以下を順番に確認してみてください。
- 保存し忘れがないか(Rork Companion を使っている場合、同期タイムラグがあります)
- 同じコンポーネントが複数ファイルに存在していないか
- 子コンポーネントの中にも同じパターンが潜んでいないか
- キャッシュが残っていないか(Metro Bundler を再起動すると解決することがあります)
Metro Bundler のキャッシュクリアは npx expo start --clear で行えます。
このエラーを一度きちんと理解しておくと、次からは5秒で原因が見抜けるようになります。まず最後に触った部分の && 演算子と数値型を確認してみてください。それで8割は解決するはずです。