Rork で生成されたコードを少し書き換えたら、いきなり赤い画面に「undefined is not an object (evaluating 'user.profile.name')」と表示された経験はありませんか。私も最初は「null チェックを足せば直るんでしょ」と高をくくっていたのですが、実際には参照しているプロパティそのものではなく、その上のオブジェクトが期待と違う形で返ってきている、というケースがほとんどでした。
このエラーは Hermes(Rork が標準で使う JavaScript エンジン)が「アクセスしようとした親が undefined だった」と教えてくれているメッセージです。原因は意外と少ないパターンに収束しますので、闇雲に ?. を足す前に、まず読み方とよくある落とし穴を押さえておくと、再発をぐっと減らせます。
まずエラーメッセージの『括弧の中』を必ず読む
このエラーで一番もったいないのが、undefined is not an object の文字だけ見て焦って手当てしてしまうことです。注目すべきはその後ろの (evaluating 'user.profile.name') の部分で、Hermes がどの式を評価したときに失敗したかをかなり正確に教えてくれます。
ERROR undefined is not an object (evaluating 'user.profile.name')
この場合、問題は user.profile.name の評価ですが、Hermes の判定基準は「name を取り出そうとしたときに user.profile が undefined だった」です。つまり犯人は profile プロパティであって、user 自体や name ではない可能性が高い、ということになります。
デバッグの第一歩は『1つ手前で止めて中身を見る』こと
Rork で動かしているとスタックトレースが minify されたファイル名(例: index.bundle:135432)になりがちで、行番号からは原因が辿りにくいです。私は次のように一段ずつ console.log を仕込む方法を使っています。
// app/screens/Profile.tsx
function ProfileScreen({ route }) {
const userId = route.params?.userId;
// ❌ いきなり最深部にアクセスしてエラー
// return <Text>{user.profile.name}</Text>;
// ✅ 段階的に中身を確認
const { data: user } = useUser(userId);
console.log('[Profile] user:', user);
console.log('[Profile] user.profile:', user?.profile);
if (!user?.profile) {
return <Text>プロフィールを読み込み中...</Text>;
}
return <Text>{user.profile.name}</Text>;
}期待出力は [Profile] user: { id: 1, name: '...' } のように出るはずなのに、実際には [Profile] user.profile: undefined と出る、というように犯人がはっきり見えます。?. を足すこと自体は対症療法ですので、まず「なぜ profile が来ていないのか」を必ず確認してから防御コードを書くようにしています。
典型的な原因 5 つと、それぞれの直し方
1. API レスポンスのキー名が想定と違う
これが一番多いパターンだと感じています。バックエンド側で profile を userProfile にリネームしたのに、フロント側だけが古い名前のまま、という事故です。
// ❌ レスポンス構造が変わったのに気付いていない
const { data } = await fetch('/api/me').then(r => r.json());
return data.profile.name; // → undefined is not an object
// ✅ レスポンスを一度確認するクセをつける
const json = await fetch('/api/me').then(r => r.json());
console.log('me response:', json);
const profile = json.user?.profile ?? json.profile ?? null;
if (!profile) throw new Error('profile が API から返ってきませんでした');
return profile.name;2. useState の初期値が undefined のまま render される
// ❌ 初期値なしの useState に、初回render で深いアクセスする
const [user, setUser] = useState(); // 初期値が undefined
useEffect(() => {
fetchUser().then(setUser);
}, []);
return <Text>{user.email}</Text>; // ← 初回 render で爆発
// ✅ 初期値を設定し、未ロード時の表示を分ける
const [user, setUser] = useState(null);
useEffect(() => { fetchUser().then(setUser); }, []);
if (!user) return <ActivityIndicator />;
return <Text>{user.email}</Text>;3. ネストした props の destructuring 忘れ
ナビゲーション周りでよく踏みます。useRoute や useLocalSearchParams の戻り値はキーが存在しないと undefined を返します。
// ❌ params が空のときに userId を取り出そうとして爆発
const { params: { userId } } = useRoute();
// ✅ 一段ずつ optional chaining で受ける
const route = useRoute();
const userId = route.params?.userId;
if (!userId) {
// 開発中は throw すると気付きやすい
if (__DEV__) console.warn('userId がありません', route);
return null;
}4. ネイティブモジュールの import を忘れている
@react-native-async-storage/async-storage のように、Expo prebuild が必要なライブラリで起こりがちです。Rork のプレビュー上は動いていても、実機ビルドだとモジュールが解決されず undefined のまま import される、という現象です。
import AsyncStorage from '@react-native-async-storage/async-storage';
// 実機で undefined になっていないかを開発時に確認
if (__DEV__ && !AsyncStorage) {
console.error('AsyncStorage が読み込めていません。expo prebuild を確認してください');
}EAS Build のログに Could not find module の警告が出ていないかも合わせて確認すると、原因の切り分けが早くなります。
5. useEffect で setState する前に値を参照している
非同期で取得した値を、まだ存在しない段階で読みに行ってしまうケースです。
// ❌ items がまだ未定義の段階で .map にかかる
useEffect(() => {
fetch('/api/items').then(r => r.json()).then(setItems);
}, []);
return items.map(i => <Item key={i.id} item={i} />);
// ✅ 配列なら空配列で初期化しておくのが安全
const [items, setItems] = useState([]); // ← undefined ではなく []
return items.map(i => <Item key={i.id} item={i} />);配列の場合は useState([])、オブジェクトの場合は useState(null) にして「未ロード」と「中身が空」を区別できる状態を作るのが、私の中での定石です。
それでも特定できないときの最終手段
これらをチェックしても直らない場合、Hermes のスタックトレースの読み方を一段深めるのが有効です。Rork から Companion アプリに繋ぐと開発ビルドで sourcemap が有効になり、minify 前のファイル名と行番号が出るようになります。さらに、エラーが画面のどこで起きているかを切り分けるために、コンポーネントツリーを ErrorBoundary で部分的に囲み、原因の枝を機械的に絞り込む方法もあります。
function SafeBox({ name, children }) {
return (
<ErrorBoundary fallback={<Text>{name} で失敗しました</Text>}>
{children}
</ErrorBoundary>
);
}
// ✅ 怪しい枝を1つずつ ErrorBoundary で包んで切り分ける
<SafeBox name="ProfileHeader">
<ProfileHeader user={user} />
</SafeBox>
<SafeBox name="PostsList">
<PostsList userId={user?.id} />
</SafeBox>「画面全体が落ちる」を「特定のセクションだけが落ちる」に変えると、後の console.log もピンポイントで仕掛けられるようになります。
再発を減らすための日々の習慣
このエラーを根本的に減らすには、TypeScript で API レスポンスの型を厳密に書き、undefined の可能性を型レベルで強制するのが一番効きます。zod などのバリデーションを通せば、レスポンス形が変わったときにフロントの型と実体がずれることを防げます。型まわりのつまずきは TypeScript の型エラーが消えないときの直し方 にもまとめていますので、合わせて読んでみてください。React Native ランタイムで起きる他のエラーパターンも整理した React Native / Expo のランタイムエラー修正ガイド も参考になるはずです。
全体を振り返って
このエラーに出会ったら、まず (evaluating '...') の式をそのまま読み、一段手前のオブジェクトの実体を console.log で確認する、というクセを付けてみてください。?. を足して赤画面が消えても、データの形がそもそも想定と違っていれば、別の場所で必ず再発します。今日の自分のコードに useState() で初期値を省略している箇所がないかだけでも、ぜひ一度 grep してみると、思わぬ地雷が見つかるかもしれません。