fetch と useEffect でデータを取得し、FlatList で一覧表示する。行の文言から data と renderItem を辿る第5話です。
API とリスト — 取得したデータを画面に並べる
これまでの記事では、画面に出ていた文言はコード内に直書きされていました。第2話 のカウンターも、第4話 の入力欄も、値はアプリ内の state だけで完結していました。
実アプリでは、サーバーから取ったデータを一覧表示することが多いです。「この行のタイトルはどこから来ているのか」と感じたときも、読み方の軸は同じです。見えている文言から逆算する。第5話では fetch と useEffect で投稿一覧を取得し、FlatList で並べながら、1 行のタイトル → 配列 → 取得処理を辿る練習をします。
この記事で扱うこと
| 章 | 実装(A) | 読み解き(B) |
|---|---|---|
| データの形 | JSONPlaceholder のレスポンス確認 | id / title / body の対応 |
| 取得 | fetch + useEffect、loading / error | エラー文言 → catch / 条件分岐 |
| 一覧 | FlatList + renderItem | 行の文言 → item → data 配列 |
| 第4話との接続 | GET で一覧を受け取る | POST 送信との役割の違い |
| まとめ | 次に進む順番 | 第6話(シリーズ最終話)への導線 |
前提: 第2話(useState)。第4話のフォームは任意です。
1. データの形を先に確認する
本記事では、認証不要の公開 API JSONPlaceholder を使います。投稿一覧の URL は次のとおりです。
https://jsonplaceholder.typicode.com/posts
ブラウザで開くか、ターミナルで curl すると、JSON の配列が返ります。先頭の 1 件はおおよそ次の形です。
{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit..."
}
本記事では id・title・body だけ使います。TypeScript では次のように型を定義します。
type Post = {
id: number;
title: string;
body: string;
};
一覧の見た目を読み解くときは、画面上に見えている title の文字列が、この JSON の title フィールドに対応している、と押さえておけば十分です。
2. fetch と useEffect — 取得の最小例
第2話と同様、1 画面・1 ファイルに絞ります。エントリファイルを次の内容に差し替えてください。
import { useEffect, useState } from "react";
import {
ActivityIndicator,
FlatList,
StyleSheet,
Text,
View,
} from "react-native";
type Post = { id: number; title: string; body: string };
export default function PostListScreen() {
const [posts, setPosts] = useState<Post[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
async function loadPosts() {
try {
const res = await fetch(
"https://jsonplaceholder.typicode.com/posts?_limit=10"
);
if (!res.ok) throw new Error("データの取得に失敗しました");
const data: Post[] = await res.json();
setPosts(data);
} catch (e) {
setError(e instanceof Error ? e.message : "不明なエラー");
} finally {
setLoading(false);
}
}
loadPosts();
}, []);
if (loading) {
return (
<View style={styles.center}>
<ActivityIndicator />
<Text>読み込み中...</Text>
</View>
);
}
if (error) {
return (
<View style={styles.center}>
<Text style={styles.error}>{error}</Text>
</View>
);
}
return (
<FlatList
data={posts}
keyExtractor={(item) => String(item.id)}
renderItem={({ item }) => (
<View style={styles.row}>
<Text style={styles.title}>{item.title}</Text>
</View>
)}
contentContainerStyle={styles.list}
/>
);
}
const styles = StyleSheet.create({
center: {
flex: 1,
justifyContent: "center",
alignItems: "center",
gap: 8,
},
error: {
color: "#dc2626",
},
list: {
padding: 16,
gap: 12,
},
row: {
paddingVertical: 12,
borderBottomWidth: 1,
borderColor: "#e2e8f0",
},
title: {
fontSize: 16,
fontWeight: "600",
},
});
bunx expo start で起動すると、まず「読み込み中…」が出て、続けて投稿タイトルの一覧が表示されます。
ポイントは次の 4 つです。
useEffect(..., [])は画面が表示されたときに 1 回だけfetchするpostsは第2話のcountと同様 state。ただし型は 配列(Post[])- 表示は loading / error / 成功 の 3 分岐に分ける
- 成功時は
FlatListのdata={posts}とrenderItemで各行を描画する
| 画面の状態 | 条件 | 見えるもの |
|---|---|---|
| 読み込み中 | loading === true | スピナーと「読み込み中…」 |
| エラー | error に文字列がある | 赤いエラーメッセージ |
| 成功 | 上記以外 | 投稿タイトルのリスト |
3. 読み解きの練習 — 行のタイトルから取得処理まで
第1話以降と同じ型で、いまリストに見えている文言から入ります。
- 一覧の投稿タイトルを 1 つ選び、エディタで全文検索する(英語の長いタイトルでも、一部の単語だけでも可)
renderItem内の{item.title}であることを確認するFlatListのdata={posts}からpostsstate を辿るsetPosts(data)を探し、fetch→res.json()まで遡る
| 見ているもの | 辿り先 |
|---|---|
| 行のタイトル文字列 | renderItem → item.title |
| 行の数・順番 | data={posts} の配列 |
| 「読み込み中…」 | loading state と if (loading) |
| 「データの取得に失敗しました」 | error state と catch |
読み解きのキーワードは次のとおりです。
行の文言 → renderItem → data → posts → fetch → useEffect
2 行目以降も同じ renderItem パターンで描画されています。行の見た目(余白・区切り線)を変えたいときは styles.row や styles.title を辿ります。
4. FlatList の要点
短いリストなら ScrollView と map でも表示できます。件数が増えると、画面外の行まで一度に描画しようとして重くなりやすいです。FlatList は 見えている行付近だけ を効率よく描画するため、一覧画面でよく使われます。
keyExtractor={(item) => String(item.id)} は、各行を React が識別するためのキーです。id がユニークなら、リストの更新時に警告が出にくくなります。
React Query や SWR、ページネーション、pull-to-refresh は本記事のスコープ外です。
5. つまずきやすい点
| 症状 | 原因の目安 |
|---|---|
| ずっと「読み込み中…」 | setLoading(false) が finally にない、fetch が完了していない |
| 真っ白なリスト | data が空、または renderItem が何も返していない |
| 同じキー警告 | keyExtractor がない、または id が重複している |
| 実機で取得できない | オフラインなど。JSONPlaceholder は HTTPS なので、通常はネット接続があれば動く |
オフラインのときは error 分岐に入り、エラーメッセージが表示されます。その文言から catch まで辿るのも、読み解きの練習になります。
6. 第4話との接続
第4話のフォームでは、名前を入力して「送信」ボタンで console.log しました。実アプリでは、その送信が POST でサーバーに届き、別の画面で GET して一覧表示する、という流れがよくあります。
本記事は GET で一覧を受け取る 側だけを扱います。第4話の handleSubmit の中身を、将来 fetch の POST に差し替えるイメージでつながります。用語だけ整理すると、送信は POST、一覧は GET です。
7. まとめ
実装の順番
useEffectでマウント時にfetchするloading/error/ 成功の 3 状態を分けるFlatListのdataとrenderItemで行を描画する
読み解きの順番
- リスト行の文言を検索する
item.xxxとdata配列を辿るsetPostsからfetchまで遡る
ここまでできれば、API 由来の一覧画面を読める土台ができています。第6話(シリーズ最終話)では、保存・取得処理を含めた 読み解きの総復習 を行います。
- 第2話: useState で画面を動かす
- 第4話: フォーム入力とバリデーション(任意)
- 第6話: AI 生成 RN コードの読み方 — 最初に見る 3 箇所