![ReactでfetchとSuspenseを使用してloading…を表示する方法](https://iwb.jp/wp-content/uploads/2024/06/react-fetch-suspense-loading-code.png)
fetchとSuspenseでloading…を表示
Reactでfetchを使用してデータを読み込み中に「loading…」と表示させたいときは、以下のようにuseStateで読み込み中のフラグの変数を作成して、条件分岐で「loading…」を表示させているケースが多いです。
const [isLoading, setIsLoading] = useState(false)
return (
<>
<div>
<button onClick={onClickHandler}>データを取得する</button>
</div>
<div>
{isLoading ? (
<p>loading...</p>
) : (
products.map((product) => (
<p key={product.id}>{product.id} {product.title}</p>
))
)}
{isError && <p style={{ color: 'red' }}>エラーが発生しました</p>}
</div>
</>
)
しかし、現在のReactにはSuspenseというコンポーネントがレンダリングされる前に、非同期操作が完了するまで待つ機能があります。
これを使用すればuseStateのisLoadingを使わずに読込中に「loading…」を表示させるコードを書くことができます。
return (
<>
<div>
<button onClick={onClickHandler}>データを取得する</button>
</div>
<Suspense fallback={<p>loading...</p>}>
<SuspendProducts />
</Suspense>
</>
)
Suspenseを使用したコードに変更する方法
まず、Suspenseを使用していない以下のようなコードがあったとします。
import { useState } from 'react'
type Product = {
id: number
title: string
}
function App() {
const [products, setProducts] = useState<Product[]>([])
const [isLoading, setIsLoading] = useState(false)
const [isClicked, setIsClicked] = useState(false)
const [isError, setIsError] = useState(false)
const onClickHandler = async() => {
if (isClicked) return
setIsLoading(true)
setIsClicked(true)
setIsError(false)
try {
await new Promise(resolve => setTimeout(resolve, 1000))
const res = await fetch('https://dummyjson.com/products')
if (!res.ok) setIsError(true)
const data = await res.json()
setProducts(data.products)
} catch (error) {
setIsError(true)
} finally {
setIsLoading(false)
}
}
return (
<>
<div>
<button onClick={onClickHandler}>データを取得する</button>
</div>
<div>
{isLoading ? (
<p>loading...</p>
) : (
products.map((product) => (
<p key={product.id}>{product.id} {product.title}</p>
))
)}
{isError && <p style={{ color: 'red' }}>エラーが発生しました</p>}
</div>
</>
)
}
export default App
Reactでfetchで読み込み時にloading…を表示したサンプル(Suspenseなし)
Suspenseを使用したコードへの修正手順
1. reactからSuspenseをimportします。
import { useState, Suspense } from 'react'
2. 「const [isLoading, setIsLoading] = useState(false)」は使わないので削除します。
3. onClickHandlerをsetIsClicked(true)のフラグ切り替えだけの処理にします。
const onClickHandler = () => setIsClicked(true)
4. ProductsResponseの型を新たに作成します。
type ProductsResponse = {
products: Product[]
}
5. fetchでデータを取得するためのgetProductsData関数とコンポーネント用のSuspendProducts関数を作成します。
const getProductsData = async(): Promise<ProductsResponse> => {
await new Promise(resolve => setTimeout(resolve, 1000))
const res = await fetch('https://dummyjson.com/products')
return res.ok ? res.json() : setIsError(true)
}
const SuspendProducts = () => {
if (isError) {
return <p style={{ color: 'red' }}>エラーが発生しました</p>
}
if (isClicked && products.length === 0) {
throw getProductsData()
.then((data) => setProducts(data.products))
.catch(() => setIsError(true))
}
return (
products.map((product) => (
<p key={product.id}>{product.id} {product.title}</p>
))
)
}
6. Appのreturn内のコードをSuspenseを使用したコードに変えます。
return (
<>
<div>
<button onClick={onClickHandler}>データを取得する</button>
</div>
<Suspense fallback={<p>loading...</p>}>
<SuspendProducts />
</Suspense>
</>
)
以上の手順が完了すれば、以下のようなコードとなり、読込中はSuspenseのfallbackで指定した「loading...」が表示されて、fetchでの読み込み後にJSONの内容(SuspendProducts)が表示されます。
修正後の完成したコードは以下のとおりです。
import { useState, Suspense } from 'react'
type Product = {
id: number
title: string
}
type ProductsResponse = {
products: Product[]
}
const App = () => {
const [products, setProducts] = useState<Product[]>([])
const [isClicked, setIsClicked] = useState(false)
const [isError, setIsError] = useState(false)
const onClickHandler = () => setIsClicked(true)
const getProductsData = async(): Promise<ProductsResponse> => {
await new Promise(resolve => setTimeout(resolve, 1000))
const res = await fetch('https://dummyjson.com/products')
return res.ok ? res.json() : setIsError(true)
}
const SuspendProducts = () => {
if (isError) {
return <p style={{ color: 'red' }}>エラーが発生しました</p>
}
if (isClicked && products.length === 0) {
throw getProductsData()
.then((data) => setProducts(data.products))
.catch(() => setIsError(true))
}
return (
products.map((product) => (
<p key={product.id}>{product.id} {product.title}</p>
))
)
}
return (
<>
<div>
<button onClick={onClickHandler}>データを取得する</button>
</div>
<Suspense fallback={<p>loading...</p>}>
<SuspendProducts />
</Suspense>
</>
)
}
export default App
だいぶ大雑把に説明しましたが、実際にコードを書いて動かしたほうがわかりやすいと思ったので、細かい説明は割愛しました。
Suspense版のサンプルも用意しましたので、実際に実行して確かめてみてください。
Reactでfetchで読み込み時にloading…を表示したサンプル(Suspenseあり)