Skip to content

Commit 2fb76f3

Browse files
committed
fix(webapp): stop hydration errors on the Tasks page
The per-row Running and Activity cells stream in via defer(); a streamed Suspense boundary still pending when the page hydrates gets bailed to client rendering and throws React #421. Render those cells client-side so there is no SSR boundary to bail.
1 parent 01b8dcf commit 2fb76f3

2 files changed

Lines changed: 34 additions & 17 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
area: webapp
3+
type: fix
4+
---
5+
6+
Stop the Tasks page from logging React hydration errors for the per-row running and activity stats.

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam._index/route.tsx

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { type ActionFunctionArgs, type LoaderFunctionArgs } from "@remix-run/ser
55
import type { TaskRunStatus } from "@trigger.dev/database";
66
import type { PanelHandle } from "@window-splitter/react";
77
import { Fragment, Suspense, useCallback, useEffect, useMemo, useRef, useState } from "react";
8+
import { ClientOnly } from "remix-utils/client-only";
89
import { Bar, BarChart, ReferenceLine, Tooltip, type TooltipProps, YAxis } from "recharts";
910
import { TypedAwait, typeddefer, useTypedLoaderData } from "remix-typedjson";
1011
import { BeakerIcon } from "~/assets/icons/BeakerIcon";
@@ -443,27 +444,37 @@ function TaskRow({
443444
<TaskFileName fileName={item.filePath} variant="extra-extra-small" />
444445
</TableCell>
445446
<TableCell to={rowPath}>
446-
<Suspense fallback={<Spinner color="blue" className="size-3" />}>
447-
<TypedAwait resolve={runningStates} errorElement={<FailedToLoadStats />}>
448-
{(data) => <RunningCell state={data[item.slug]} />}
449-
</TypedAwait>
450-
</Suspense>
447+
{/* Render the deferred stats client-side. A streamed Suspense boundary still pending at
448+
hydration otherwise bails to client rendering and throws React #421. */}
449+
<ClientOnly fallback={<Spinner color="blue" className="size-3" />}>
450+
{() => (
451+
<Suspense fallback={<Spinner color="blue" className="size-3" />}>
452+
<TypedAwait resolve={runningStates} errorElement={<FailedToLoadStats />}>
453+
{(data) => <RunningCell state={data[item.slug]} />}
454+
</TypedAwait>
455+
</Suspense>
456+
)}
457+
</ClientOnly>
451458
</TableCell>
452459
<TableCell to={rowPath} actionClassName="py-1.5">
453460
<div style={{ width: ACTIVITY_CELL_WIDTH, height: ACTIVITY_CHART_HEIGHT }}>
454461
<div hidden={isPanelAnimating}>
455-
<Suspense fallback={<TaskActivityBlankState />}>
456-
<TypedAwait resolve={hourlyActivity} errorElement={<FailedToLoadStats />}>
457-
{(data) => {
458-
const taskData = data[item.slug];
459-
return taskData && taskData.length > 0 ? (
460-
<TaskActivityGraph activity={taskData} />
461-
) : (
462-
<TaskActivityBlankState />
463-
);
464-
}}
465-
</TypedAwait>
466-
</Suspense>
462+
<ClientOnly fallback={<TaskActivityBlankState />}>
463+
{() => (
464+
<Suspense fallback={<TaskActivityBlankState />}>
465+
<TypedAwait resolve={hourlyActivity} errorElement={<FailedToLoadStats />}>
466+
{(data) => {
467+
const taskData = data[item.slug];
468+
return taskData && taskData.length > 0 ? (
469+
<TaskActivityGraph activity={taskData} />
470+
) : (
471+
<TaskActivityBlankState />
472+
);
473+
}}
474+
</TypedAwait>
475+
</Suspense>
476+
)}
477+
</ClientOnly>
467478
</div>
468479
</div>
469480
</TableCell>

0 commit comments

Comments
 (0)