tarako314
@tarako314

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

Next.js 14 Suspenseが上手く動作しない

解決したいこと

Next.js 14 Suspenseが上手く動作しない

発生している問題・エラー

Next 14 でfetch中にSuspenseでloading..と出したかったのですが、処理を書き間違えているのかローディング中に何も表示されません

該当するソースコード

/src/app/sample/page.tsx
import Sample from '@/app/components/Sample'
import { Suspense } from 'react'

const page = () => {
  return (
    <>
      <section>
        <Suspense fallback={<p className="text-red-200">Loading weather...</p>}>
          <Sample />
        </Suspense>
      </section>
    </>
  )
}

export default page

/src/app/components/Sample.tsx
'use client'
import { useEffect, useState } from 'react'

interface Data {
  userId: number
  id: number
  title: string
}
const Sample = () => {
  const [data, setData] = useState<Data[]>([])

  useEffect(() => {
    getApi()
  }, [])

  const getApi = async () => {
    const res = await fetch('https://jsonplaceholder.typicode.com/todos/1')
    const data = await res.json()
    console.log(data)
    setData([data])
  }
  return (
    <>
      <div>
        <h1 className="font-bold text-2xl">Test Suspense</h1>
        {data?.map((value: Data, index) => (
          <div key={index}>
            <div>{value.userId}</div>
            <div>{value.id}</div>
            <div>{value.title}</div>
          </div>
        ))}
      </div>
    </>
  )
}

export default Sample

【ローディング中(現在)】
image.png
【データ取得後】
image.png

自分で試したこと

公式通り記述はしたつもりですがダメでした...
https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming

0

1Answer

SuspenseuseEffectやイベントハンドラ内で行われたデータ取得で有効になりません(今回はuseEffectで取得しているので動作してません)。
Suspenseのfallbackが呼び出されるのはサスペンス対応のデータソースだけです。
詳しくはReactのSuspenseについてのドキュメントを参考にしてください。

Nextjsにおけるデータ取得についてはドキュメントで言及されていますので、それが参考になると思います。

また、今回の場合layout.tsxsection要素の部分を書いて、loading.tsxfallbackを載せて、Sampleコンポーネントの内容をpage.tsxに書くのがよさそうです。
参考

動作検証していないので、参考程度にしてください。

layout.tsx
import type { ReactNode } from 'react';

const layout = ({
  children
}: {
  children: ReactNode
}) => {
  return (
    <section>
      {children}
    </section>
  );
};

export default layout;
loading.tsx
const loading = () => {
  return <p className="text-red-200">Loading weather...</p>;
};

export default loading;
page.tsx
interface Data {
  userId: number
  id: number
  title: string
}

const getDate = async (): Promise<Data> => {
  const res = await fetch('https://jsonplaceholder.typicode.com/todos/1');
  return res.json();
};

const page = async () => {
  const data = await getDate();

  return (
    <div>
      <h1 className="font-bold text-2xl">Test Suspense</h1>
      {data?.map((value: Data) => (
        <div key={value.userId}>
          <div>{value.userId}</div>
          <div>{value.id}</div>
          <div>{value.title}</div>
        </div>
      ))}
    </div>
  );
};

export default page

少し話がそれますが、配列の要素を展開してレンダーするときのkeyは配列のインデックスではなく、userIdのようなユニークな値にする必要があります。参考

1Like

Comments

  1. @tarako314

    Questioner

    Suspenseが使用できる箇所は結構限りがあるんですね...
    今回提示して頂いたようにloading.tsを駆使した実装の仕方にしてみます
    又、indexの件も今まで何となく使用していましたが記事をみて正しい使い方ではなかったことを理解でき、とても参考になりました。有難うございました

Your answer might help someone💌