T
TanStack•2y ago
environmental-rose

Implementing Data Polling with Stale Data in Next.js 14 using React Query

Hey everyone! 👋 Let's delve into a scenario: In the app I'm developing, there's a flight details page designed to display real-time flight information. The challenge is that this data needs to be updated every few seconds. Currently, my approach involves a server-side component that initially fetches the data and then passes it down to the flight details component on the client-side. Additionally, there's a loading.tsx component that displays a loading skeleton, as depicted in the images. Let's review the code below:
// flights/[flightId]/page.tsx

export async function getFlightDetails(
flightId: string,
): Promise<LiveFlightDetail | null> {
const url = `${API_BASE_URL}/networks/flights/${flightId}`;

const options: RequestInit = {
method: "GET",
headers: {
"Content-Type": "application/json",
},
};

const result = await fetch(url, options);
const data = await result.json();

if (result.status !== 200) {
return null;
}

return data;
}

interface FlightsDetailPageProps {
params: {
flightId: string;
};
}

export default async function FlightsDetailPage({
params: { flightId },
}: FlightsDetailPageProps) {
if (!flightId) {
return notFound();
}

const data = await getFlightDetails(flightId);

if (!data) {
return notFound();
}

return <FlightDetails initialData={data} />;
}
// flights/[flightId]/page.tsx

export async function getFlightDetails(
flightId: string,
): Promise<LiveFlightDetail | null> {
const url = `${API_BASE_URL}/networks/flights/${flightId}`;

const options: RequestInit = {
method: "GET",
headers: {
"Content-Type": "application/json",
},
};

const result = await fetch(url, options);
const data = await result.json();

if (result.status !== 200) {
return null;
}

return data;
}

interface FlightsDetailPageProps {
params: {
flightId: string;
};
}

export default async function FlightsDetailPage({
params: { flightId },
}: FlightsDetailPageProps) {
if (!flightId) {
return notFound();
}

const data = await getFlightDetails(flightId);

if (!data) {
return notFound();
}

return <FlightDetails initialData={data} />;
}
No description
No description
1 Reply
environmental-rose
environmental-roseOP•2y ago
// flights/[flightId]/_components/flight-details.tsx

"use client"

interface FlightDetailsProps {
initialData: LiveFlightDetail;
}

export function FlightDetails({ initialData }: FlightDetailsProps) {
const { data, error } = useQuery({
initialData,
queryKey: ["flight-details", initialData.id],
queryFn: async () => getFlightDetails(initialData.id),
refetchOnReconnect: true,
refetchOnWindowFocus: true,
refetchInterval: FLIGHTS_REFETCH_INTERVAL_IN_MILLISECONDS,
});

if (error || !data) {
return notFound();
}

return (
<>
{/* UI elements... */}
</>
)
}
// flights/[flightId]/_components/flight-details.tsx

"use client"

interface FlightDetailsProps {
initialData: LiveFlightDetail;
}

export function FlightDetails({ initialData }: FlightDetailsProps) {
const { data, error } = useQuery({
initialData,
queryKey: ["flight-details", initialData.id],
queryFn: async () => getFlightDetails(initialData.id),
refetchOnReconnect: true,
refetchOnWindowFocus: true,
refetchInterval: FLIGHTS_REFETCH_INTERVAL_IN_MILLISECONDS,
});

if (error || !data) {
return notFound();
}

return (
<>
{/* UI elements... */}
</>
)
}
Before proceeding, it's essential to acknowledge that I'm uncertain if this approach is feasible... The Challenge: I aim to have loading skeleton only during the server fetch for the initial data and employ SWR behavior for subsequent updates on the client-side. Essentially, I want the skeleton to appear only when the page loads for the first time and not while data is being updated in the background. This ensures the old data remains visible during the request, and once the new data is ready, it seamlessly replaces the old without causing layout shifts.

Did you find this page helpful?