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 @@...@@ -1,6 +1,6 @@
1import type { GitBranch, ThreadId } from "@t3tools/contracts";1import type { GitBranch, ThreadId } from "@t3tools/contracts";
2import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";2import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
3import { useCallback, useEffect, useState } from "react";3import { useEffect, useState } from "react";
44
5import { newCommandId } from "../lib/utils";5import { newCommandId } from "../lib/utils";
6import {6import {
...@@ -46,16 +46,12 @@ export default function BranchToolbar({...@@ -46,16 +46,12 @@ export default function BranchToolbar({
46 const setThreadBranchAction = useStore((state) => state.setThreadBranch);46 const setThreadBranchAction = useStore((state) => state.setThreadBranch);
47 const setThreadErrorAction = useStore((state) => state.setThreadError);47 const setThreadErrorAction = useStore((state) => state.setThreadError);
48 const draftThread = useComposerDraftStore((store) => store.getDraftThread(threadId));48 const draftThread = useComposerDraftStore((store) => store.getDraftThread(threadId));
49 const serverThread = useStore(49 const serverThread = useStore((state) =>
50 useCallback((state) => state.threads.find((thread) => thread.id === threadId), [threadId]),50 state.threads.find((thread) => thread.id === threadId),
51 );51 );
52 const activeProjectId = serverThread?.projectId ?? draftThread?.projectId ?? null;52 const activeProjectId = serverThread?.projectId ?? draftThread?.projectId ?? null;
53 const activeProject = useStore(53 const activeProject = useStore((state) =>
54 useCallback(54 activeProjectId ? state.projects.find((project) => project.id === activeProjectId) : undefined,
55 (state) =>
56 activeProjectId ? state.projects.find((project) => project.id === activeProjectId) : undefined,
57 [activeProjectId],
58 ),
59 );55 );
60 const setDraftThreadContext = useComposerDraftStore((store) => store.setDraftThreadContext);56 const setDraftThreadContext = useComposerDraftStore((store) => store.setDraftThreadContext);
61 const queryClient = useQueryClient();57 const queryClient = useQueryClient();
apps/web/src/components/ChatView.tsx+12-28
...@@ -490,18 +490,14 @@ export default function ChatView({ threadId }: ChatViewProps) {...@@ -490,18 +490,14 @@ export default function ChatView({ threadId }: ChatViewProps) {
490 const clearDraftThread = useComposerDraftStore((store) => store.clearDraftThread);490 const clearDraftThread = useComposerDraftStore((store) => store.clearDraftThread);
491 const setDraftThreadContext = useComposerDraftStore((store) => store.setDraftThreadContext);491 const setDraftThreadContext = useComposerDraftStore((store) => store.setDraftThreadContext);
492 const draftThread = useComposerDraftStore((store) => store.draftThreadsByThreadId[threadId] ?? null);492 const draftThread = useComposerDraftStore((store) => store.draftThreadsByThreadId[threadId] ?? null);
493 const serverThread = useStore(493 const serverThread = useStore((state) =>
494 useCallback((state) => state.threads.find((thread) => thread.id === threadId), [threadId]),494 state.threads.find((thread) => thread.id === threadId),
495 );495 );
496 const fallbackDraftProjectId = draftThread?.projectId ?? null;496 const fallbackDraftProjectId = draftThread?.projectId ?? null;
497 const fallbackDraftProject = useStore(497 const fallbackDraftProject = useStore((state) =>
498 useCallback(498 fallbackDraftProjectId
499 (state) =>499 ? state.projects.find((project) => project.id === fallbackDraftProjectId)
500 fallbackDraftProjectId500 : undefined,
501 ? state.projects.find((project) => project.id === fallbackDraftProjectId)
502 : undefined,
503 [fallbackDraftProjectId],
504 ),
505 );501 );
506 const promptRef = useRef(prompt);502 const promptRef = useRef(prompt);
507 const [isDragOverComposer, setIsDragOverComposer] = useState(false);503 const [isDragOverComposer, setIsDragOverComposer] = useState(false);
...@@ -601,12 +597,8 @@ export default function ChatView({ threadId }: ChatViewProps) {...@@ -601,12 +597,8 @@ export default function ChatView({ threadId }: ChatViewProps) {
601 const activeLatestTurn = activeThread?.latestTurn ?? null;597 const activeLatestTurn = activeThread?.latestTurn ?? null;
602 const latestTurnSettled = isLatestTurnSettled(activeLatestTurn, activeThread?.session ?? null);598 const latestTurnSettled = isLatestTurnSettled(activeLatestTurn, activeThread?.session ?? null);
603 const activeProjectId = activeThread?.projectId ?? null;599 const activeProjectId = activeThread?.projectId ?? null;
604 const activeProject = useStore(600 const activeProject = useStore((state) =>
605 useCallback(601 activeProjectId ? state.projects.find((project) => project.id === activeProjectId) : undefined,
606 (state) =>
607 activeProjectId ? state.projects.find((project) => project.id === activeProjectId) : undefined,
608 [activeProjectId],
609 ),
610 );602 );
611603
612 useEffect(() => {604 useEffect(() => {
...@@ -3541,20 +3533,12 @@ const OpenInPicker = memo(function OpenInPicker({...@@ -3541,20 +3533,12 @@ const OpenInPicker = memo(function OpenInPicker({
3541 ] satisfies { label: string; Icon: Icon; value: EditorId }[];3533 ] satisfies { label: string; Icon: Icon; value: EditorId }[];
3542 const primaryOption = options.find(({ value }) => value === lastEditor);3534 const primaryOption = options.find(({ value }) => value === lastEditor);
35433535
3544 const activeThread = useStore(3536 const activeThread = useStore((state) =>
3545 useCallback(3537 activeThreadId ? state.threads.find((thread) => thread.id === activeThreadId) : undefined,
3546 (state) =>
3547 activeThreadId ? state.threads.find((thread) => thread.id === activeThreadId) : undefined,
3548 [activeThreadId],
3549 ),
3550 );3538 );
3551 const activeProjectId = activeThread?.projectId ?? null;3539 const activeProjectId = activeThread?.projectId ?? null;
3552 const activeProject = useStore(3540 const activeProject = useStore((state) =>
3553 useCallback(3541 activeProjectId ? state.projects.find((project) => project.id === activeProjectId) : undefined,
3554 (state) =>
3555 activeProjectId ? state.projects.find((project) => project.id === activeProjectId) : undefined,
3556 [activeProjectId],
3557 ),
3558 );3542 );
35593543
3560 const openInEditor = useCallback(3544 const openInEditor = useCallback(
apps/web/src/components/DiffPanel.tsx+4-12
...@@ -167,20 +167,12 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) {...@@ -167,20 +167,12 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) {
167 });167 });
168 const diffSearch = useSearch({ strict: false, select: (search) => parseDiffRouteSearch(search) });168 const diffSearch = useSearch({ strict: false, select: (search) => parseDiffRouteSearch(search) });
169 const activeThreadId = routeThreadId;169 const activeThreadId = routeThreadId;
170 const activeThread = useStore(170 const activeThread = useStore((state) =>
171 useCallback(171 activeThreadId ? state.threads.find((thread) => thread.id === activeThreadId) : undefined,
172 (state) =>
173 activeThreadId ? state.threads.find((thread) => thread.id === activeThreadId) : undefined,
174 [activeThreadId],
175 ),
176 );172 );
177 const activeProjectId = activeThread?.projectId ?? null;173 const activeProjectId = activeThread?.projectId ?? null;
178 const activeProject = useStore(174 const activeProject = useStore((state) =>
179 useCallback(175 activeProjectId ? state.projects.find((project) => project.id === activeProjectId) : undefined,
180 (state) =>
181 activeProjectId ? state.projects.find((project) => project.id === activeProjectId) : undefined,
182 [activeProjectId],
183 ),
184 );176 );
185 const activeCwd = activeThread?.worktreePath ?? activeProject?.cwd;177 const activeCwd = activeThread?.worktreePath ?? activeProject?.cwd;
186 const { turnDiffSummaries, inferredCheckpointTurnCountByTurnId } =178 const { turnDiffSummaries, inferredCheckpointTurnCountByTurnId } =
apps/web/src/routes/_chat.$threadId.tsx+2-2
...@@ -139,8 +139,8 @@ function ChatThreadRouteView() {...@@ -139,8 +139,8 @@ function ChatThreadRouteView() {
139 select: (params) => ThreadId.makeUnsafe(params.threadId),139 select: (params) => ThreadId.makeUnsafe(params.threadId),
140 });140 });
141 const search = Route.useSearch();141 const search = Route.useSearch();
142 const threadExists = useStore(142 const threadExists = useStore((state) =>
143 useCallback((state) => state.threads.some((thread) => thread.id === threadId), [threadId]),143 state.threads.some((thread) => thread.id === threadId),
144 );144 );
145 const draftThreadExists = useComposerDraftStore(145 const draftThreadExists = useComposerDraftStore(
146 (store) => Object.hasOwn(store.draftThreadsByThreadId, threadId),146 (store) => Object.hasOwn(store.draftThreadsByThreadId, threadId),