目次
API通信とデータ取得
最小構成:useEffect + fetch のパターン
まずは「マウント時に1回だけAPIを叩いて一覧を表示する」最小パターンから。
import { useState, useEffect } from 'react'
export default function PostList() {
const [posts, setPosts] = useState([])
useEffect(() => {
async function fetchPosts() {
const res = await fetch('https://jsonplaceholder.typicode.com/posts')
const data = await res.json()
setPosts(data.slice(0, 5)) // 5件だけに絞る
}
fetchPosts()
}, []) // 初回マウント時だけ
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}ここで押さえたい流れ:
- 初回レンダリング後、
useEffect(..., [])が1回だけ走る fetch⇒res.json()でデータ取得setPostsで state を更新- state が変わったので再レンダリング ⇒
<li>一覧が表示される
最初は 「マウント時1回だけ」「一覧表示だけ」 に絞って感覚を掴むのがおすすめです。
loading と error を持つ
実務レベルに近づけるには、ローディング状態とエラー状態も持ったほうがよいです。
import { useState, useEffect } from 'react'
export default function PostList() {
const [posts, setPosts] = useState([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
let isCancelled = false
async function fetchPosts() {
try {
setLoading(true)
setError(null)
const res = await fetch('https://jsonplaceholder.typicode.com/posts')
if (!res.ok) {
throw new Error('ネットワークエラー')
}
const data = await res.json()
if (!isCancelled) {
setPosts(data.slice(0, 5))
}
} catch (err) {
if (!isCancelled) {
setError(err.message || '何らかのエラーが発生しました')
}
} finally {
if (!isCancelled) {
setLoading(false)
}
}
}
fetchPosts()
return () => {
isCancelled = true
}
}, [])
if (loading) return <p>読み込み中...</p>
if (error) return <p style={{ color: 'red' }}>エラー: {error}</p>
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}ポイント整理:
- 状態を3種類持つ
posts(データ)loading(読み込み中か?)error(エラーメッセージ)
- 通信開始前に
setLoading(true)、setError(null) - 通信成功 ⇒
setPosts⇒setLoading(false) - 通信失敗 ⇒
setError(...)⇒setLoading(false) - UI側では
loadingならローディング表示errorがあればエラー表示- どちらでもないときに一覧表示
この「状態3点セット + 条件分岐でUI切り替え」は、API連携画面の定番パターンです。
条件付きで再取得:検索キーワードやページ番号
次に、ユーザー入力に応じてAPIを叩き直すパターンを見てみます。
例:キーワード検索で毎回APIリクエスト
import { useState, useEffect } from 'react'
export default function SearchUsers() {
const [keyword, setKeyword] = useState('') // 入力中のキーワード
const [query, setQuery] = useState('') // 実際に検索に使うキーワード
const [users, setUsers] = useState([])
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
useEffect(() => {
if (!query) return // 空文字なら検索しない
let isCancelled = false
async function fetchUsers() {
try {
setLoading(true)
setError(null)
const res = await fetch(
`https://api.example.com/users?keyword=${encodeURIComponent(query)}`
)
if (!res.ok) throw new Error('検索に失敗しました')
const data = await res.json()
if (!isCancelled) {
setUsers(data)
}
} catch (err) {
if (!isCancelled) {
setError(err.message)
}
} finally {
if (!isCancelled) {
setLoading(false)
}
}
}
fetchUsers()
return () => {
isCancelled = true
}
}, [query]) // ← query が変わるたびに検索
function handleSubmit(e) {
e.preventDefault()
setQuery(keyword.trim())
}
return (
<div>
<form onSubmit={handleSubmit}>
<input
value={keyword}
onChange={(e) => setKeyword(e.target.value)}
placeholder="ユーザー名で検索"
/>
<button type="submit">検索</button>
</form>
{loading && <p>検索中...</p>}
{error && <p style={{ color: 'red' }}>エラー: {error}</p>}
<ul>
{users.map((u) => (
<li key={u.id}>{u.name}</li>
))}
</ul>
</div>
)
}設計のポイント:
- 入力中のテキスト
keywordと、実際に検索に使うqueryを分けることで、「入力されるたびにAPIを叩く」のを防ぎ、「フォーム送信時だけAPIを呼ぶ」ように制御している useEffectの依存配列にはqueryを入れ、「検索条件が変わったときだけ」リクエストを投げる
同じ考え方で、page(ページ番号)をstateに持ち、[page] を依存にしてページング付き一覧を作ることもできます。
データ表示時の基本テクニック:map と key
APIから配列が返ってきたら、ほぼ必ず map でJSXに変換します。
<ul>
{items.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>keyは React が要素を識別するために使うIDのようなもの- 理想は APIのレコードIDなど、一意な値 を使う
- arrayのindexをkeyに使うのは「並べ替えや削除が発生しないリスト」に限定
key の目的は 再レンダリング時に「どの要素がどれか」を見分けるため なので、id などの安定したユニークキーがあるなら、基本それ一択と覚えてOKです。
fetch と axios ざっくり比較
学習段階では fetch だけで十分ですが、実務では axios もよく使われます。
ざっくり違いだけ押さえておきましょう。
fetch
- ブラウザ標準API(追加ライブラリ不要)
- デフォルトではエラー時も
res.okを自分でチェックする必要がある - JSON変換も
await res.json()を毎回書く
axios
- 別途インストールが必要:
npm install axios - 自動でJSONパースして
res.dataに入れてくれる - HTTPステータスが4xx/5xxなら例外を投げてくれるので、エラー処理が書きやすい
axios版の例:
import axios from 'axios'
import { useState, useEffect } from 'react'
export default function PostList() {
const [posts, setPosts] = useState([])
useEffect(() => {
async function fetchPosts() {
const res = await axios.get('https://jsonplaceholder.typicode.com/posts')
setPosts(res.data.slice(0, 5))
}
fetchPosts()
}, [])
// 以降は同じ
}どちらを使うかはプロジェクトポリシー次第ですが、仕組みの理解と基礎練習は fetch で十分です。
簡単な「再読込」ボタンを付けてみる
API連携画面では、手動で再取得するボタンもよくあります。
import { useState, useEffect } from 'react'
export default function RandomUser() {
const [user, setUser] = useState(null)
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
const [reloadKey, setReloadKey] = useState(0)
useEffect(() => {
let isCancelled = false
async function fetchUser() {
try {
setLoading(true)
setError(null)
const res = await fetch('https://randomuser.me/api/')
if (!res.ok) throw new Error('取得に失敗しました')
const data = await res.json()
const u = data.results[0]
if (!isCancelled) {
setUser({
name: `${u.name.first} ${u.name.last}`,
email: u.email,
})
}
} catch (err) {
if (!isCancelled) {
setError(err.message)
}
} finally {
if (!isCancelled) {
setLoading(false)
}
}
}
fetchUser()
return () => {
isCancelled = true
}
}, [reloadKey]) // ← reloadKey が変わるたびに再取得
function handleReload() {
setReloadKey((k) => k + 1)
}
return (
<div>
<button onClick={handleReload} disabled={loading}>
再読込
</button>
{loading && <p>読み込み中...</p>}
{error && <p style={{ color: 'red' }}>エラー: {error}</p>}
{user && (
<div>
<p>名前: {user.name}</p>
<p>メール: {user.email}</p>
</div>
)}
</div>
)
}処理の流れ:
reloadKey というstateを用意し、その値を useEffect の依存配列に入れることで、ボタンを押したときに reloadKey をインクリメント ⇒ useEffectが再実行 ⇒ 再読込
まとめ
useEffect+fetchが 「ReactでAPIを叩く基本の型」- 実務では
dataだけでなく、loading/errorの3つのstateをセットで持つことが多い - 条件付き再取得は
useEffectの依存配列にquery/page/reloadKeyなどを入れて制御 map + keyで一覧表示、keyには安定したIDを使う- axiosを使うとエラー処理やJSONパースが少し楽になるが、基礎は
fetchで十分
コメント