fix(desktop): infinite render loop opening a chat (Maximum update depth)

The Conversation selector `useStore(s => s.messages[address] ?? [])`
allocates a fresh empty array on every call when the chat has no cached
messages. Zustand compares selector results with `===`, so the new []
is different from the previous [], marking the slice as "changed",
which re-renders Conversation, which calls the selector again, which
produces another new []... "Maximum update depth exceeded" inside
seconds.

Fix: module-level `const EMPTY_MESSAGES: Message[] = []` returned as the
fallback. Same object reference every render, zustand's === bails
early, no re-render.

This crash only showed up after opening a chat whose messages hadn't
been cached yet — picking any entry in ChatList that hadn't received
an envelope would hang the renderer. PaneBoundary (added in the prior
commit) now catches it visibly instead of blacking out the whole
window, but we still want the real fix.
This commit is contained in:
vsecoder
2026-04-22 18:58:57 +03:00
parent 481d4d2fa8
commit 7e6fe2c2a0

View File

@@ -16,10 +16,18 @@ import { sendEnvelope, resolveRecipientKeys } from '@/lib/relay';
import { appendMessage as persist } from '@/lib/storage'; import { appendMessage as persist } from '@/lib/storage';
import type { Message } from '@/lib/types'; import type { Message } from '@/lib/types';
// A module-level stable reference for "no messages yet". Without this the
// selector `s.messages[address] ?? []` allocates a fresh empty array on
// every render when the conversation has no cached entries, which zustand
// sees as a changed value → triggers another render → new empty array
// again → "Maximum update depth exceeded". Returning the exact same
// reference every time breaks the cycle.
const EMPTY_MESSAGES: Message[] = [];
export function Conversation({ address }: { address: string }): React.ReactElement { export function Conversation({ address }: { address: string }): React.ReactElement {
const keyFile = useStore(s => s.keyFile); const keyFile = useStore(s => s.keyFile);
const contact = useStore(s => s.contacts.find(c => c.address === address)); const contact = useStore(s => s.contacts.find(c => c.address === address));
const messages = useStore(s => s.messages[address] ?? []); const messages = useStore(s => s.messages[address] ?? EMPTY_MESSAGES);
const clearUnread = useStore(s => s.clearUnread); const clearUnread = useStore(s => s.clearUnread);
const appendMsg = useStore(s => s.appendMessage); const appendMsg = useStore(s => s.appendMessage);