stcobbeS
Convex Community16mo ago
22 replies
stcobbe

Recommended pattern for initializing useQuery with server loaded value

Hey folks,

Is there a recommended NextJS-compatible pattern for initializing a useQuery hook with a result from the server so that initial page load is very snappy but subsequent changes are fully reactive? (I realize this involves double fetching)

e.g.

export default async function ChatServer({
  chatId,
}: {
  chatId: ChatId | null;
}) {
  const initialMessages = chatId
    ? await fetchQuery(api.chats.listMessagesByChatId, {
        chatId,
      })
    : undefined;
  return (
    <ChatClient
      initialChatId={chatId}
      initialMessages={initialMessages}
    />
  );
}


export default function ChatClient({
  initialChatId,
  initialMessages,
}: {
  initialChatId: ChatId | null;
  initialMessages?: ChatMessage[];
}) {
  const messages = useQueryWithInit(
    api.chats.listMessagesByChatId,
    initialMessages,
    chatId ? { chatId } : "skip",
  );

  return (
    <>
      <div className="w-full h-full flex flex-col">
        <Content
          messages={messages}
        />
      </div>
    </>
  );
}


Here's a hypothetical implementation of such a function:

export function useQueryWithInit<T extends FunctionReference<"query">>(
  queryFunction: T,
  initialValue: FunctionReturnType<T> | undefined,
  ...args: OptionalRestArgsOrSkip<T>
): FunctionReturnType<T> | undefined {
  const queryResult = useQuery(queryFunction, ...args);

  const hasReceivedValue = useRef(false);

  if (args[0] === "skip") {
    return null;
  }

  if (queryResult !== undefined) {
    // Mark that we've received a value
    hasReceivedValue.current = true;
    // Return the query result immediately
    return queryResult;
  }

  if (!hasReceivedValue.current) {
    return initialValue;
  }

  return undefined;
}
Was this page helpful?