Pass labelCreatedAt from useTrendingTags to useTagSparklines to avoid duplicate REQ

This commit is contained in:
Alex Gleason
2026-02-23 21:51:20 -06:00
parent c85225127f
commit 3a8903f8e0
2 changed files with 28 additions and 30 deletions
+5 -2
View File
@@ -90,11 +90,14 @@ export function TrendSparkline({ data }: { data: number[] }) {
export function RightSidebar() {
const isXl = useIsXl();
const { data: trendingTags, isLoading: tagsLoading } = useTrendingTags(isXl);
const { data: trendingTagsResult, isLoading: tagsLoading } = useTrendingTags(isXl);
const { data: rawHotPosts, isLoading: hotLoading } = useSortedPosts('hot', 5, isXl);
const { data: latestAccounts, isLoading: accountsLoading } = useLatestAccounts(isXl);
const { muteItems } = useMuteList();
const trendingTags = trendingTagsResult?.tags;
const labelCreatedAt = trendingTagsResult?.labelCreatedAt ?? 0;
const hotPosts = useMemo(() => {
if (!rawHotPosts || muteItems.length === 0) return rawHotPosts;
return rawHotPosts.filter((e) => !isEventMuted(e, muteItems));
@@ -102,7 +105,7 @@ export function RightSidebar() {
// Fetch real sparkline data for the visible trending tags
const visibleTags = useMemo(() => (trendingTags ?? []).slice(0, 5).map((t) => t.tag), [trendingTags]);
const { data: sparklineData, isLoading: sparklinesLoading } = useTagSparklines(visibleTags, isXl && visibleTags.length > 0);
const { data: sparklineData, isLoading: sparklinesLoading } = useTagSparklines(visibleTags, labelCreatedAt, isXl && visibleTags.length > 0);
return (
<aside className="w-[300px] shrink-0 hidden xl:flex flex-col sticky top-0 h-screen overflow-y-auto pt-5 pb-3 px-5">
+23 -28
View File
@@ -16,6 +16,12 @@ export interface TrendingTag {
uses: number;
}
export interface TrendingTagsResult {
tags: TrendingTag[];
/** created_at of the label event, used to align sparkline day boundaries. */
labelCreatedAt: number;
}
/**
* Fetches trending hashtags from relay.ditto.pub via kind 1985 label events.
* These are published with L: "pub.ditto.trends" and l: "#t", "pub.ditto.trends".
@@ -24,7 +30,7 @@ export interface TrendingTag {
export function useTrendingTags(enabled = true) {
const { nostr } = useNostr();
return useQuery<TrendingTag[]>({
return useQuery<TrendingTagsResult>({
queryKey: ['trending-tags'],
queryFn: async ({ signal }) => {
const ditto = nostr.relay(DITTO_RELAY);
@@ -39,18 +45,21 @@ export function useTrendingTags(enabled = true) {
{ signal: AbortSignal.any([signal, AbortSignal.timeout(10000)]) },
);
if (events.length === 0) return [];
if (events.length === 0) return { tags: [], labelCreatedAt: 0 };
// The label event contains `t` tags for each trending hashtag.
// Tag format: ['t', hashtag, '', accounts, uses]
// index 3 = distinct accounts using the hashtag
// index 4 = total uses of the hashtag
const tTags = events[0].tags.filter(([name]) => name === 't');
return tTags.map(([, tag, , rawAccounts, rawUses]) => ({
tag: tag.toLowerCase(),
accounts: parseInt(rawAccounts || '0', 10),
uses: parseInt(rawUses || '0', 10),
}));
return {
tags: tTags.map(([, tag, , rawAccounts, rawUses]) => ({
tag: tag.toLowerCase(),
accounts: parseInt(rawAccounts || '0', 10),
uses: parseInt(rawUses || '0', 10),
})),
labelCreatedAt: events[0].created_at,
};
},
enabled,
staleTime: 5 * 60 * 1000,
@@ -258,38 +267,24 @@ function generateSparklineDays(refTimestampSecs: number): { since: number; until
* derived from the most-recent label event's created_at, and filters each
* per-day query by the specific hashtag (`#t`) so only label events that
* mention that tag are returned.
*
* @param labelCreatedAt - created_at from the useTrendingTags label event,
* used to align day boundaries without an extra round-trip.
*/
export function useTagSparklines(tags: string[], enabled = true) {
export function useTagSparklines(tags: string[], labelCreatedAt: number, enabled = true) {
const { nostr } = useNostr();
const sortedTags = [...new Set(tags.map((t) => t.toLowerCase()))].sort();
return useQuery<Map<string, number[]>>({
queryKey: ['tag-sparklines', sortedTags.join(',')],
queryKey: ['tag-sparklines', sortedTags.join(','), labelCreatedAt],
queryFn: async ({ signal }) => {
if (sortedTags.length === 0) return new Map();
if (sortedTags.length === 0 || !labelCreatedAt) return new Map();
const ditto = nostr.relay(DITTO_RELAY);
// Fetch the most-recent label event to use as the reference date,
// matching the server: `const date = new Date(label.created_at * 1000)`
const [latestLabel] = await ditto.query(
[{
kinds: [1985],
authors: [TRENDS_PUBKEY],
'#L': ['pub.ditto.trends'],
'#l': ['#t'],
limit: 1,
}],
{ signal: AbortSignal.any([signal, AbortSignal.timeout(3000)]) },
);
if (!latestLabel) {
return new Map(sortedTags.map((tag) => [tag, new Array(SPARKLINE_DAYS).fill(0)]));
}
// Generate UTC-midnight-aligned day boundaries from the label's created_at
const days = generateSparklineDays(latestLabel.created_at);
const days = generateSparklineDays(labelCreatedAt);
// Initialise empty buckets for every tag
const sparkMap = new Map<string, number[]>();