Next.jsで任意のタイミングでデータ取得するには
Next.jsの任意のタイミングでデータ取得したい
例: モーダルの表示時にデータを取得して、その結果をモーダル内に表示したい
Next.jsのデータ取得とデータ更新の考え方
任意のタイミングで解決できるデータ取得
Next.jsのデータ取得は、任意のタイミングで解決できる
ここでの解決とは、fetchが返すPromiseをawaitして戻り値を参照できるようになることを指す
Server Componentにて解決する場合
export async function ServerComponent() {
const message = await fetch("http://localhost:3000/api")
.then((res) => res.json())
.then((data) => data.message);
return <div>message: {message}</div>;
}
ServerComponentのレンダリング時に解決される
Client Componentにて解決する場合
// サーバーコンポーネント
export default function ServerComponent() {
const messagePromise = fetch("http://localhost:3000/api")
.then((res) => res.json())
.then((data) => data.message);
return <ClientComponent messagePromise={messagePromise} />;
}
// クライアントコンポーネント
"use client";
import { use } from "react";
export function ClientComponent({ messagePromise }: { messagePromise: Promise<string> }) {
const message = use(messagePromise);
return <div>message: {message}</div>;
}
ClientComponentのレンダリング時に解決される
例えば、画面の下の方のコンテンツを、表示するまで解決を遅らせることができる
任意のタイミングで開始できないデータ取得
ただし、データ取得の開始は、任意のタイミングで開始できない
データ取得は、そのロジックが書かれたサーバーコンポーネントのレンダリング時に、開始される
前提として、useEffect
で囲わないfetchは、Server Componentでしか使えない
// このコンポーネントのレンダリング時に、fetchが開始される
export async function ServerComponent() {
const message = await fetch("http://localhost:3000/api")
.then((res) => res.json())
.then((data) => data.message);
return <div>message: {message}</div>;
}
// awaitしていなくても、このコンポーネントのレンダリング時に、fetchが開始される
export default function ServerComponent() {
const messagePromise = fetch("http://localhost:3000/api")
.then((res) => res.json())
.then((data) => data.message);
return <ClientComponent messagePromise={messagePromise} />;
}
Server Componentのレンダリングは必ずpageに紐づく
そして、Server Componentのレンダリングは必ずpageに紐づく
Server Componentのレンダリングは、そのServer Componentが配置されているpageが表示されたとき、に実行される
例えば、以下のようにして「初期状態ではServer Componentを非表示にしておいて、表示にしたときにレンダリングしよう」としてもうまくいかない
// ServerComponentToFetchData.tsx
export async function ServerComponentToFetchData() {
const message = await fetch("http://localhost:3000/api")
.then((res) => res.json())
.then((data) => data.message);
return <div>message: {message}</div>;
}
// Toggle.tsx
"use client";
import { useState } from "react";
export function Toggle({ children }: { children: React.ReactNode }) {
const [show, setShow] = useState(false);
const toggle = () => setShow((prev) => !prev);
return (
<div>
{show ? (
<button onClick={toggle}>hide data</button>
) : (
<button onClick={toggle}>show data</button>
)}
{show && <>{children}</>}
</div>
);
}
// page.tsx
import { ServerComponentToFetchData } from "./ServerComponentToFetchData";
import { Toggle } from "./Toggle";
export default function Page() {
return (
<Toggle>
<ServerComponentToFetchData />
</Toggle>
);
}
なぜなら、サーバーでのレンダリング時には、Client Componentは無視されて、以下のようにレンダリングされるからだ
// ServerComponentToFetchData.tsx
export async function ServerComponentToFetchData() {
const message = await fetch("http://localhost:3000/api")
.then((res) => res.json())
.then((data) => data.message);
return <div>message: {message}</div>;
}
// page.tsx
import { ServerComponentToFetchData } from "./ServerComponentToFetchData";
export default function Page() {
return (
<>
<ServerComponentToFetchData />
</>
);
}
// `Toggle.tsx`はクライアント側(ブラウザ)でレンダリングされる
よって「モーダル表示時にServer Componentをレンダリングしてデータを取得する」はできない
また、Client Componentでデータ取得を解決する方法もうまくいかない
// Toggle.tsx
"use client";
import { use, useState } from "react";
export function Toggle({ messagePromise }: { messagePromise: Promise<string> }) {
const [show, setShow] = useState(false);
const toggle = () => setShow((prev) => !prev);
let message: string | null = null;
if (show) {
message = use(messagePromise);
}
return (
<div>
{show ? (
<button onClick={toggle}>hide data</button>
) : (
<button onClick={toggle}>show data</button>
)}
{show && <div>message: {message}</div>}
</div>
);
}
// page.tsx
import { Toggle } from "./Toggle";
export default function Page() {
const messagePromise = fetch("http://localhost:3000/api")
.then((res) => res.json())
.then((data) => data.message);
return <Toggle messagePromise={messagePromise} />;
}
なぜなら、データ取得の解決はモーダル表示時にできるが、データ取得の開始はServer Componentのレンダリング時(ページアクセス時)のままだからだ
URLパラメータを使ってダイアログの表示非表示を変える
では、どうするか
Next.jsで案内されている & 多くのユースケースデメリットがあるのは、URLパラメータを使ってダイアログの表示非表示を変える手法だ
これなら、ダイアログの表示 = ページ遷移となるので、ページ遷移時にServer Componentをレンダリングしてデータを取得すれば良い。ダイアログ非表示の場合、ダイアログコンポーネントは表示されないので、無駄なデータ取得は起こらない
//
また、URLに画面状態を保つことはweb的に良い
Pageに紐づかないServer Componentはない(lazy?)
Hackなアプローチ Server Actionsによるデータ取得
あまり推奨されないが、Server Actionsをデータ取得の目的で使うことができる
Server ActionsはJSXを返すことができる
任意のタイミングでデータ取得したいなら
ここで、Route Handler(旧API Handler)の出番
Next.jsでも、Server Componentが全てを代替するとは言われておらず、依然としてRoute Handlerも提供されている
SWR
Suspenseも対応してるよ

client内にServer componentを明示していないfetchを差し込んだ場合
fetchはclient、server両方で使えるのでclient component扱いにならないでしょうか?
nextやったことないですが気になりました
<Toggle>
<ServerComponentToFetchData />
</Toggle>
Next.jsでは、コンポーネントはデフォルトでReact Server Componentsとなるので、明示しなくても Server Component扱いになるはずです。
ちなみに、このコードで
<Toggle>
<ServerComponentToFetchData />
</Toggle>
ServerComponentToFetchData.tsx
の先頭に'use client'
を記述したら
Error: async/await is not yet supported in Client Components, only Server Components. This error is often caused by accidentally adding
'use client'
to a module that was originally written for the server.
となりました。