From a07dada0c9ba80976692ee14e256da0a2d6b0112 Mon Sep 17 00:00:00 2001
From: nsfisis <nsfisis@gmail.com>
Date: Sun, 1 Feb 2026 10:16:35 +0900
Subject: refactor(atoms): migrate to jotai-tanstack-query from custom
 reloadable atoms

Replace custom createReloadableAtom/createReloadableAtomFamily with
atomWithSuspenseQuery from jotai-tanstack-query, leveraging TanStack
Query's built-in caching, invalidation, and Suspense support.

- Add @tanstack/query-core and jotai-tanstack-query dependencies
- Create shared QueryClient instance (src/client/queryClient.ts)
- Migrate all data atoms (decks, cards, study, noteTypes) to atomWithSuspenseQuery
- Update page components to use .data destructuring and queryClient.invalidateQueries()
- Update all test files to use QueryClient for data hydration
- Remove src/client/atoms/utils.ts (no longer needed)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---
 src/client/pages/StudyPage.test.tsx | 24 ++++++++++++++----------
 1 file changed, 14 insertions(+), 10 deletions(-)

(limited to 'src/client/pages/StudyPage.test.tsx')

diff --git a/src/client/pages/StudyPage.test.tsx b/src/client/pages/StudyPage.test.tsx
index a366f35..dfadea9 100644
--- a/src/client/pages/StudyPage.test.tsx
+++ b/src/client/pages/StudyPage.test.tsx
@@ -1,19 +1,15 @@
 /**
  * @vitest-environment jsdom
  */
+import { QueryClient } from "@tanstack/query-core";
 import { cleanup, render, screen, waitFor } from "@testing-library/react";
 import userEvent from "@testing-library/user-event";
 import { createStore, Provider } from "jotai";
+import { queryClientAtom } from "jotai-tanstack-query";
 import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
 import { Route, Router } from "wouter";
 import { memoryLocation } from "wouter/memory-location";
-import {
-	authLoadingAtom,
-	type StudyCard,
-	type StudyData,
-	studyDataAtomFamily,
-} from "../atoms";
-import { clearAtomFamilyCaches } from "../atoms/utils";
+import { authLoadingAtom, type StudyCard, type StudyData } from "../atoms";
 import { StudyPage } from "./StudyPage";
 
 interface RenderOptions {
@@ -72,6 +68,8 @@ vi.mock("../api/client", () => ({
 
 import { ApiClientError, apiClient } from "../api/client";
 
+let testQueryClient: QueryClient;
+
 const mockDeck = {
 	id: "deck-1",
 	name: "Japanese Vocabulary",
@@ -121,14 +119,15 @@ function renderWithProviders({
 	const { hook } = memoryLocation({ path, static: true });
 	const store = createStore();
 	store.set(authLoadingAtom, false);
+	store.set(queryClientAtom, testQueryClient);
 
 	// Extract deckId from path
 	const deckIdMatch = path.match(/\/decks\/([^/]+)/);
 	const deckId = deckIdMatch?.[1] ?? "deck-1";
 
-	// Hydrate atom if initial data provided
+	// Seed query cache if initial data provided
 	if (initialStudyData !== undefined) {
-		store.set(studyDataAtomFamily(deckId), initialStudyData);
+		testQueryClient.setQueryData(["decks", deckId, "study"], initialStudyData);
 	}
 
 	return render(
@@ -145,6 +144,11 @@ function renderWithProviders({
 describe("StudyPage", () => {
 	beforeEach(() => {
 		vi.clearAllMocks();
+		testQueryClient = new QueryClient({
+			defaultOptions: {
+				queries: { staleTime: Number.POSITIVE_INFINITY, retry: false },
+			},
+		});
 		vi.mocked(apiClient.getTokens).mockReturnValue({
 			accessToken: "access-token",
 			refreshToken: "refresh-token",
@@ -161,7 +165,7 @@ describe("StudyPage", () => {
 	afterEach(() => {
 		cleanup();
 		vi.restoreAllMocks();
-		clearAtomFamilyCaches();
+		testQueryClient.clear();
 	});
 
 	describe("Loading and Initial State", () => {
-- 
cgit v1.3-1-g0d28

