I've got a static site and I'm trying to render a simple list of items retrieved from Supabase. I had this working in React 17, but with React 18 it throws this error sporadically, and the fallback doesn't reliably appear. This seems to mostly happen when doing a hard page refresh. Authentication is done via cookie and server-side middleware.
Error: This Suspense boundary received an update before it finished hydrating. This caused the boundary to switch to client rendering. The usual way to fix this is to wrap the original update in startTransition.
//index.tsx (page component)
import { AddEventButton } from '@/components/index';
import { ComponentWithLayout } from '@/types/definitions';
import { Suspense } from 'react';
import { getNavbarLayout } from '@/layouts/NavbarLayout';
import UpcomingEventListSkeleton from '@/components/upcomingEventList/UpcomingEventList.skeleton';
import dynamic from 'next/dynamic';
const UpcomingEventList = dynamic(
() => import('@/components/upcomingEventList/UpcomingEventList'),
{ suspense: true }
);
const UpcomingEventsPage: ComponentWithLayout = () => {
return (
<div className="mx-auto lg:w-1/2 xs:w-full">
<div className="space-y-2">
<Suspense fallback={<UpcomingEventListSkeleton />}>
<UpcomingEventList />
</Suspense>
</div>
<AddEventButton />
</div>
);
};
UpcomingEventsPage.getLayout = getNavbarLayout;
export default UpcomingEventsPage;
//UpcomingEventList.tsx
import { CalendarEvent } from '@/types/definitions';
import { CalendarEventConfiguration } from '@/utils/appConfig';
import { UpcomingEvent } from './components/upcomingEvent/UpcomingEvent';
import { supabaseClient } from '@supabase/auth-helpers-nextjs';
import React from 'react';
import dayjs from 'dayjs';
import useSWR from 'swr';
/**
* Shows a list of Upcoming Event components
* @returns UpcomingEventList component
*/
export const UpcomingEventList = () => {
const { data } = useSWR(
'upcomingEvents',
async () =>
await supabaseClient
.from<CalendarEvent>(CalendarEventConfiguration.database.tableName)
.select('*, owner: user_profile(full_name, avatar)')
.limit(10)
.order('end', { ascending: true })
.gt('end', dayjs(new Date()).toISOString()),
{
refreshInterval: 3000,
suspense: true,
}
);
return (
<>
{data && data.data?.length === 0 && (
<div className="card shadow bg-base-100 mx-auto w-full border text-center p-5">
<div className="text-base-content font-medium">
No upcoming events scheduled
</div>
<div className="text-sm opacity-50">
Events can be scheduled by clicking the purple button in the
bottom-right corner.
</div>
</div>
)}
{data &&
data.data?.map(
({ id, owner, start, end, number_of_guests, privacy_requested }) => (
<UpcomingEvent
key={id}
title={owner?.full_name}
startDate={start}
endDate={end}
numberOfGuests={number_of_guests}
privacyRequested={privacy_requested}
avatarSrc={owner?.avatar}
/>
)
)}
</>
);
};
export default UpcomingEventList;
//UpcomingEventListSkeleton.tsx
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import {
faEye,
faPlayCircle,
faStopCircle,
faUser,
} from '@fortawesome/free-solid-svg-icons';
import React from 'react';
const UpcomingEventListSkeleton = () => (
<>
{[...Array(5).keys()].map((v, i) => (
<UpcomingEventSkeleton key={i} />
))}
</>
);
export default UpcomingEventListSkeleton;
const UpcomingEventSkeleton = () => (
<div className="card shadow bg-base-100 text-base-content font-medium mx-auto w-full border">
<div className="flex items-center m-2 animate-pulse">
<div className="mr-5 ml-1">
<div className="rounded-full bg-gray-200 h-12 w-12"></div>
</div>
<span className="space-y-1">
<div className="text-xl">
<div className="h-8 bg-gray-200 rounded w-56"></div>
</div>
<div
className="text-sm opacity-50"
style={{
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
overflow: 'hidden',
width: '20em',
}}
>
<div className="grid grid-cols-10 grid-rows-4">
<div>
<FontAwesomeIcon icon={faPlayCircle} className="col-span-1" />
</div>
<div className="h-2 bg-gray-200 rounded col-span-9 mt-1 w-36"></div>
<div>
<FontAwesomeIcon icon={faStopCircle} />
</div>
<div className="h-2 bg-gray-200 rounded col-span-9 mt-1 w-36"></div>
<div>
<FontAwesomeIcon icon={faUser} />
</div>
<div className="h-2 bg-gray-200 rounded col-span-9 mt-1 w-36"></div>
<div>
<FontAwesomeIcon icon={faEye} />
</div>
<div className="h-2 bg-gray-200 rounded col-span-9 mt-1 w-36"></div>
</div>
</div>
</span>
</div>
</div>
);
I've tried swapping the components around, storing the results via useEffect to force client-side rendering, and nothing seems to make this error go away or even change behavior. A simple example that removes the custom components and replaces them with simple strings suffers the exact same issue.
I don't understand how useTransition would help me here, nor how I would even make use of it with useSWR as my fetching mechanism. I've searched around and most people who have this issue seem to be using SSR, and not SSG as I am.
Any help is appreciated.
