Skip to content

Commit 8b2adcc

Browse files
Reduce provider update pill rerenders
Co-authored-by: Julius Marminge <juliusmarminge@users.noreply.github.com>
1 parent 4f0f24f commit 8b2adcc

1 file changed

Lines changed: 106 additions & 49 deletions

File tree

apps/web/src/components/sidebar/SidebarProviderUpdatePill.tsx

Lines changed: 106 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
import { useNavigate } from "@tanstack/react-router";
22
import type { ServerProvider } from "@t3tools/contracts";
33
import { CircleCheckIcon, DownloadIcon, LoaderIcon, TriangleAlertIcon, XIcon } from "lucide-react";
4-
import { useCallback, useEffect, useState, type CSSProperties } from "react";
4+
import {
5+
useCallback,
6+
useEffect,
7+
useRef,
8+
useState,
9+
type CSSProperties,
10+
type Dispatch,
11+
type SetStateAction,
12+
type TransitionEvent,
13+
} from "react";
514

615
import { useServerProviders } from "../../rpc/serverState";
716
import {
@@ -37,48 +46,36 @@ function latestProviderCheckedAt(
3746
);
3847
}
3948

40-
export function SidebarProviderUpdatePill() {
41-
const navigate = useNavigate();
42-
const providers = useServerProviders();
43-
const [dismissedKeys, setDismissedKeys] = useState<ReadonlySet<string>>(() => new Set());
44-
const [renderedView, setRenderedView] = useState<ProviderUpdateSidebarPillView | null>(null);
45-
const [pendingView, setPendingView] = useState<ProviderUpdateSidebarPillView | null>(null);
46-
const [exitingKey, setExitingKey] = useState<string | null>(null);
47-
const [dismissAfterExitKey, setDismissAfterExitKey] = useState<string | null>(null);
48-
const [visibleAfterIso, setVisibleAfterIso] = useState<string | undefined>();
49-
const effectiveVisibleAfterIso = visibleAfterIso ?? latestProviderCheckedAt(providers);
50-
const view = getProviderUpdateSidebarPillView(providers, {
51-
...(effectiveVisibleAfterIso !== undefined
52-
? { visibleAfterIso: effectiveVisibleAfterIso }
53-
: {}),
54-
dismissedKeys,
55-
});
49+
function useProviderUpdateVisibleAfterIso(
50+
providers: ReadonlyArray<Pick<ServerProvider, "checkedAt">>,
51+
): string | undefined {
52+
const visibleAfterIsoRef = useRef<string | undefined>(undefined);
5653

57-
useEffect(() => {
58-
if (visibleAfterIso === undefined && effectiveVisibleAfterIso !== undefined) {
59-
setVisibleAfterIso(effectiveVisibleAfterIso);
60-
}
61-
}, [effectiveVisibleAfterIso, visibleAfterIso]);
54+
if (visibleAfterIsoRef.current === undefined) {
55+
visibleAfterIsoRef.current = latestProviderCheckedAt(providers);
56+
}
6257

63-
const openProviderSettings = useCallback(() => {
64-
void navigate({ to: "/settings/providers" });
65-
}, [navigate]);
66-
const displayedView = renderedView ?? view;
67-
const dismissAfterVisibleMs = displayedView?.dismissAfterVisibleMs;
68-
const viewKey = displayedView?.key ?? null;
69-
const showDismissProgress =
70-
dismissAfterVisibleMs !== undefined &&
71-
displayedView?.tone !== "loading" &&
72-
exitingKey !== viewKey;
58+
return visibleAfterIsoRef.current;
59+
}
60+
61+
function useDisplayedProviderUpdatePill(input: {
62+
view: ProviderUpdateSidebarPillView | null;
63+
setDismissedKeys: Dispatch<SetStateAction<ReadonlySet<string>>>;
64+
}) {
65+
const { view, setDismissedKeys } = input;
66+
const [renderedView, setRenderedView] = useState<ProviderUpdateSidebarPillView | null>(view);
67+
const [exitingKey, setExitingKey] = useState<string | null>(null);
68+
const pendingViewRef = useRef<ProviderUpdateSidebarPillView | null>(null);
69+
const dismissAfterExitKeyRef = useRef<string | null>(null);
7370

7471
const startExit = useCallback(
7572
(key: string, nextView: ProviderUpdateSidebarPillView | null, dismissKey?: string) => {
7673
if (exitingKey === key) {
7774
return;
7875
}
79-
setPendingView(nextView);
76+
pendingViewRef.current = nextView;
77+
dismissAfterExitKeyRef.current = dismissKey ?? null;
8078
setExitingKey(key);
81-
setDismissAfterExitKey(dismissKey ?? null);
8279
},
8380
[exitingKey],
8481
);
@@ -103,6 +100,46 @@ export function SidebarProviderUpdatePill() {
103100
}
104101
}, [exitingKey, renderedView, startExit, view]);
105102

103+
const displayedView = renderedView ?? view;
104+
const onTransitionEnd = useCallback(
105+
(event: TransitionEvent<HTMLDivElement>) => {
106+
if (event.target !== event.currentTarget) {
107+
return;
108+
}
109+
if (!displayedView || exitingKey !== displayedView.key) {
110+
return;
111+
}
112+
if (dismissAfterExitKeyRef.current === displayedView.key) {
113+
setDismissedKeys((previous) => new Set(previous).add(displayedView.key));
114+
}
115+
setRenderedView(pendingViewRef.current);
116+
pendingViewRef.current = null;
117+
dismissAfterExitKeyRef.current = null;
118+
setExitingKey(null);
119+
},
120+
[displayedView, exitingKey, setDismissedKeys],
121+
);
122+
123+
return {
124+
displayedView,
125+
exitingKey,
126+
onTransitionEnd,
127+
startExit,
128+
};
129+
}
130+
131+
function useProviderUpdateAutoDismiss(input: {
132+
dismissAfterVisibleMs: number | undefined;
133+
exitingKey: string | null;
134+
startExit: (
135+
key: string,
136+
nextView: ProviderUpdateSidebarPillView | null,
137+
dismissKey?: string,
138+
) => void;
139+
viewKey: string | null;
140+
}) {
141+
const { dismissAfterVisibleMs, exitingKey, startExit, viewKey } = input;
142+
106143
useEffect(() => {
107144
if (!dismissAfterVisibleMs || !viewKey) {
108145
return;
@@ -116,6 +153,40 @@ export function SidebarProviderUpdatePill() {
116153

117154
return () => window.clearTimeout(timeoutId);
118155
}, [dismissAfterVisibleMs, exitingKey, startExit, viewKey]);
156+
}
157+
158+
export function SidebarProviderUpdatePill() {
159+
const navigate = useNavigate();
160+
const providers = useServerProviders();
161+
const [dismissedKeys, setDismissedKeys] = useState<ReadonlySet<string>>(() => new Set());
162+
const effectiveVisibleAfterIso = useProviderUpdateVisibleAfterIso(providers);
163+
const view = getProviderUpdateSidebarPillView(providers, {
164+
...(effectiveVisibleAfterIso !== undefined
165+
? { visibleAfterIso: effectiveVisibleAfterIso }
166+
: {}),
167+
dismissedKeys,
168+
});
169+
170+
const openProviderSettings = useCallback(() => {
171+
void navigate({ to: "/settings/providers" });
172+
}, [navigate]);
173+
const { displayedView, exitingKey, onTransitionEnd, startExit } = useDisplayedProviderUpdatePill({
174+
view,
175+
setDismissedKeys,
176+
});
177+
const dismissAfterVisibleMs = displayedView?.dismissAfterVisibleMs;
178+
const viewKey = displayedView?.key ?? null;
179+
const showDismissProgress =
180+
dismissAfterVisibleMs !== undefined &&
181+
displayedView?.tone !== "loading" &&
182+
exitingKey !== viewKey;
183+
184+
useProviderUpdateAutoDismiss({
185+
dismissAfterVisibleMs,
186+
exitingKey,
187+
startExit,
188+
viewKey,
189+
});
119190

120191
if (!displayedView) {
121192
return null;
@@ -130,21 +201,7 @@ export function SidebarProviderUpdatePill() {
130201
? "pointer-events-none translate-y-1.5 opacity-0"
131202
: "translate-y-0 opacity-100"
132203
}`}
133-
onTransitionEnd={(event) => {
134-
if (event.target !== event.currentTarget) {
135-
return;
136-
}
137-
if (!displayedView || exitingKey !== displayedView.key) {
138-
return;
139-
}
140-
if (dismissAfterExitKey === displayedView.key) {
141-
setDismissedKeys((previous) => new Set(previous).add(displayedView.key));
142-
}
143-
setRenderedView(pendingView);
144-
setPendingView(null);
145-
setExitingKey(null);
146-
setDismissAfterExitKey(null);
147-
}}
204+
onTransitionEnd={onTransitionEnd}
148205
>
149206
{showDismissProgress ? (
150207
<div

0 commit comments

Comments
 (0)