authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-03-02 00:30:29-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-03-02 13:13:19-08:00
logd4c90d696d2546466d364cfccc86bcf2be6a7c86
tree478277e9ca05405bcd84389b9a2e41ca74b33421
parentbe4d26a1b31d96754bdadc2ab7094dfdc9c7bad9

Simplify Zustand selectors by removing memoized callbacks

- Replace `useCallback`-wrapped `useStore` selectors with inline selectors - Apply across chat, diff, branch toolbar, and chat thread route components

4 files changed, 23 insertions(+), 51 deletions(-)

apps/web/src/components/BranchToolbar.tsx+5-9
......@@ -1,6 +1,6 @@
11import type { GitBranch, ThreadId } from "@t3tools/contracts";
22import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
3import { useCallback, useEffect, useState } from "react";
3import { useEffect, useState } from "react";
44
55import { newCommandId } from "../lib/utils";
66import {
......@@ -46,16 +46,12 @@ export default function BranchToolbar({
4646 const setThreadBranchAction = useStore((state) => state.setThreadBranch);
4747 const setThreadErrorAction = useStore((state) => state.setThreadError);
4848 const draftThread = useComposerDraftStore((store) => store.getDraftThread(threadId));
49 const serverThread = useStore(
50 useCallback((state) => state.threads.find((thread) => thread.id === threadId), [threadId]),
49 const serverThread = useStore((state) =>
50 state.threads.find((thread) => thread.id === threadId),
5151 );
5252 const activeProjectId = serverThread?.projectId ?? draftThread?.projectId ?? null;
53 const activeProject = useStore(
54 useCallback(
55 (state) =>
56 activeProjectId ? state.projects.find((project) => project.id === activeProjectId) : undefined,
57 [activeProjectId],
58 ),
53 const activeProject = useStore((state) =>
54 activeProjectId ? state.projects.find((project) => project.id === activeProjectId) : undefined,
5955 );
6056 const setDraftThreadContext = useComposerDraftStore((store) => store.setDraftThreadContext);
6157 const queryClient = useQueryClient();
apps/web/src/components/ChatView.tsx+12-28
......@@ -490,18 +490,14 @@ export default function ChatView({ threadId }: ChatViewProps) {
490490 const clearDraftThread = useComposerDraftStore((store) => store.clearDraftThread);
491491 const setDraftThreadContext = useComposerDraftStore((store) => store.setDraftThreadContext);
492492 const draftThread = useComposerDraftStore((store) => store.draftThreadsByThreadId[threadId] ?? null);
493 const serverThread = useStore(
494 useCallback((state) => state.threads.find((thread) => thread.id === threadId), [threadId]),
493 const serverThread = useStore((state) =>
494 state.threads.find((thread) => thread.id === threadId),
495495 );
496496 const fallbackDraftProjectId = draftThread?.projectId ?? null;
497 const fallbackDraftProject = useStore(
498 useCallback(
499 (state) =>
500 fallbackDraftProjectId
501 ? state.projects.find((project) => project.id === fallbackDraftProjectId)
502 : undefined,
503 [fallbackDraftProjectId],
504 ),
497 const fallbackDraftProject = useStore((state) =>
498 fallbackDraftProjectId
499 ? state.projects.find((project) => project.id === fallbackDraftProjectId)
500 : undefined,
505501 );
506502 const promptRef = useRef(prompt);
507503 const [isDragOverComposer, setIsDragOverComposer] = useState(false);
......@@ -601,12 +597,8 @@ export default function ChatView({ threadId }: ChatViewProps) {
601597 const activeLatestTurn = activeThread?.latestTurn ?? null;
602598 const latestTurnSettled = isLatestTurnSettled(activeLatestTurn, activeThread?.session ?? null);
603599 const activeProjectId = activeThread?.projectId ?? null;
604 const activeProject = useStore(
605 useCallback(
606 (state) =>
607 activeProjectId ? state.projects.find((project) => project.id === activeProjectId) : undefined,
608 [activeProjectId],
609 ),
600 const activeProject = useStore((state) =>
601 activeProjectId ? state.projects.find((project) => project.id === activeProjectId) : undefined,
610602 );
611603
612604 useEffect(() => {
......@@ -3541,20 +3533,12 @@ const OpenInPicker = memo(function OpenInPicker({
35413533 ] satisfies { label: string; Icon: Icon; value: EditorId }[];
35423534 const primaryOption = options.find(({ value }) => value === lastEditor);
35433535
3544 const activeThread = useStore(
3545 useCallback(
3546 (state) =>
3547 activeThreadId ? state.threads.find((thread) => thread.id === activeThreadId) : undefined,
3548 [activeThreadId],
3549 ),
3536 const activeThread = useStore((state) =>
3537 activeThreadId ? state.threads.find((thread) => thread.id === activeThreadId) : undefined,
35503538 );
35513539 const activeProjectId = activeThread?.projectId ?? null;
3552 const activeProject = useStore(
3553 useCallback(
3554 (state) =>
3555 activeProjectId ? state.projects.find((project) => project.id === activeProjectId) : undefined,
3556 [activeProjectId],
3557 ),
3540 const activeProject = useStore((state) =>
3541 activeProjectId ? state.projects.find((project) => project.id === activeProjectId) : undefined,
35583542 );
35593543
35603544 const openInEditor = useCallback(
apps/web/src/components/DiffPanel.tsx+4-12
......@@ -167,20 +167,12 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) {
167167 });
168168 const diffSearch = useSearch({ strict: false, select: (search) => parseDiffRouteSearch(search) });
169169 const activeThreadId = routeThreadId;
170 const activeThread = useStore(
171 useCallback(
172 (state) =>
173 activeThreadId ? state.threads.find((thread) => thread.id === activeThreadId) : undefined,
174 [activeThreadId],
175 ),
170 const activeThread = useStore((state) =>
171 activeThreadId ? state.threads.find((thread) => thread.id === activeThreadId) : undefined,
176172 );
177173 const activeProjectId = activeThread?.projectId ?? null;
178 const activeProject = useStore(
179 useCallback(
180 (state) =>
181 activeProjectId ? state.projects.find((project) => project.id === activeProjectId) : undefined,
182 [activeProjectId],
183 ),
174 const activeProject = useStore((state) =>
175 activeProjectId ? state.projects.find((project) => project.id === activeProjectId) : undefined,
184176 );
185177 const activeCwd = activeThread?.worktreePath ?? activeProject?.cwd;
186178 const { turnDiffSummaries, inferredCheckpointTurnCountByTurnId } =
apps/web/src/routes/_chat.$threadId.tsx+2-2
......@@ -139,8 +139,8 @@ function ChatThreadRouteView() {
139139 select: (params) => ThreadId.makeUnsafe(params.threadId),
140140 });
141141 const search = Route.useSearch();
142 const threadExists = useStore(
143 useCallback((state) => state.threads.some((thread) => thread.id === threadId), [threadId]),
142 const threadExists = useStore((state) =>
143 state.threads.some((thread) => thread.id === threadId),
144144 );
145145 const draftThreadExists = useComposerDraftStore(
146146 (store) => Object.hasOwn(store.draftThreadsByThreadId, threadId),