Получение данных на стороне сервера в nextjs

Я экспериментирую с nextjs (исходя из реакции). Я думаю, что у меня в основном все работает, но когда я создаю серверный компонент для проверки выборки на стороне сервера, я получаю странные результаты.

Большую часть времени, когда я запускаю сервер и загружаю страницу, постоянно отображается сообщение Loading.... Затем, если я обновлю страницу, данные отобразятся так, как ожидалось. При навигации по сайту и возврате на страницу обычно отображаются данные, но иногда отображается Loading, и если это происходит, мне приходится обновлять страницу, чтобы данные снова отображались.

Я подозреваю, что это как-то связано с кешированием nextjs, но я не уверен.

Мой компонент страницы выглядит примерно так, примерно украден из документации nextjs:

let statsData: StatsType[];
let url = 'stats';

async function getData() {
    const res = await fetchDataServerSide(url);

    if (!res.ok) {
        throw new Error('Failed to fetch data')
    }
    await res.json().then(json => {
        statsData = json;
    })
}

const Dashboard = () => {
    const data = getData();

    /*if (error) return <div>Failed to load</div>*/
    if (!statsData) {
        return <div>Loading...</div>
    } else {
        return (
            <div className = {"p-6"}>
                <div className = {"pb-6"}>
                    <Stats stats = {statsData}/>
                </div>
            </div>
        )
    }
}

export default Dashboard

мой fetchDataServerSide был просто попыткой абстрагировать fetch от каждой страницы, которая мне нужна для извлечения данных, поскольку мне нужно каждый раз передавать заголовки для работы с моим бэкэндом API laravel:

async function fetchDataServerSide(url: string): Promise<Response> {
    'use server'
    let fetch_path = process.env.NEXT_PUBLIC_BACKEND_URL + '/api/' + url

    const options: RequestInit = {
        method: "GET",
        headers: {
            "Accept": "application/json",
            "Referer": process.env.APP_URL,
            "X-Requested-With": "XMLHttpRequest",
            "Content-Type": "application/json",
            "cookie": "XSRF-TOKEN = " + cookies().get('XSRF-TOKEN').value + ";laravel_session = " + cookies().get('laravel_session').value,
        }
    }

    return await fetch(fetch_path, options);
}

Есть мысли о том, что я делаю неправильно?

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
0
0
760
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Вы не ждете получения данных. Вам необходимо изменить «Панель мониторинга», чтобы она стала асинхронным компонентом, и использовать границу ожидания с резервным пользовательским интерфейсом загрузки. Он будет отображать пользовательский интерфейс загрузки до тех пор, пока не будут загружены данные, необходимые дочерним элементам. Когда данные загружаются, компонент статистики отображается с данными. Компонент ожидания используется для ожидания получения данных и после получения возвращает данные дочерним элементам.

import { notFound } from "next/navigation";
import { Suspense } from 'react';

async function Await<T>({ promise, children }: { promise: Promise<T>, children: (value: T) => JSX.Element }) {
  let data = await promise;
  if (!data) return notFound();
  return children(data);
}

const Dashboard = async () => {
  const data = getData();
  return (
    <Suspense fallback = {<div>Loading...</div>}>
      <Await promise = {data}>
        {( data ) =>  <Stats stats = {data}/>}
      </Await>
    </Suspense>
  );
};

Вам нужно преобразовать компонент Dashboard в асинхронный, это нормально, поскольку это серверный компонент. Затем дождитесь вашего getData() для условной визуализации вашего компонента статистики.

const data = await getData();

Затем, если вы хотите сохранить представление загрузки, вы можете создать скелет loading.js в той же папке маршрута:

export default function Loading() { 
   //custom loading skeleton component
   return <p>Loading...</p>
}

https://nextjs.org/docs/app/api-reference/file-conventions/loading

Похоже, вы столкнулись с проблемой асинхронного поведения в компоненте Dashboard. Функция getData является асинхронной, и вы не ждете ее завершения перед отрисовкой компонента. В результате компонент отображается с неопределенным значением statsData, что приводит к появлению сообщения «Загрузка...».

Вот как вы можете это исправить:

import { useState, useEffect } from 'react';

const Dashboard = () => {
    const [statsData, setStatsData] = useState<StatsType[] | null>(null);

    useEffect(() => {
        async function fetchData() {
            try {
                const res = await fetchDataServerSide(url);
                if (!res.ok) {
                    throw new Error('Failed to fetch data');
                }
                const json = await res.json();
                setStatsData(json);
            } catch (error) {
                console.error('Error fetching data:', error);
                // Handle error state if needed
            }
        }
        fetchData();
    }, []); // Empty dependency array ensures this effect runs only once

    if (!statsData) {
        return <div>Loading...</div>;
    } else {
        return (
            <div className = "p-6">
                <div className = "pb-6">
                    <Stats stats = {statsData} />
                </div>
            </div>
        );
    }
};

export default Dashboard;

При таком подходе:

  1. Мы используем хук useState для управления состоянием statsData.
  2. Мы используем хук useEffect для извлечения данных при монтировании компонента ([], поскольку массив зависимостей гарантирует, что он запускается только один раз).
  3. Мы обновляем состояние statsData, когда данные успешно получены.
  4. Компонент будет отображать сообщение «Загрузка...» до тех пор, пока данные не будут получены и statsData не обновится.

Это должно решить проблему несогласованного отображения данных, с которой вы столкнулись.

Другие вопросы по теме