feat(rq): migrate data-fetching hooks to React Query - #12
Conversation
- Refactored useRating to utilize useQuery for fetching ratings and useMutation for submitting ratings, improving data handling and caching. - Migrated useStats to useQuery with polling for real-time stats updates. - Updated useTakeQuiz to replace manual data fetching with useQuery for quiz and questions, maintaining local state for UI. - Created ReactQueryProvider to wrap the application with QueryClientProvider for React Query setup. - Cleaned up unused imports across various hooks after migration. - Ensured all hooks maintain the same return shape and functionality, with no UI changes.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe application adds a shared React Query provider and migrates quiz, statistics, rating, quiz-detail, quiz-bank, and quiz-taking data fetching to React Query queries and mutations with caching, invalidation, and polling. ChangesReact Query migration
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant App
participant ReactQueryProvider
participant QuizHook
participant Supabase
App->>ReactQueryProvider: render application subtree
ReactQueryProvider->>QuizHook: provide QueryClient
QuizHook->>Supabase: request quiz data
Supabase-->>QuizHook: return query data
QuizHook->>ReactQueryProvider: update query cache
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/hooks/useTakeQuiz.ts (1)
152-160: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winAwait the auto-submit insert before advancing. This branch starts
supabase.from("quiz_attempts").insert(...)without awaiting it, so thetry/catchcannot handle a failure and the hook still clears the deadline and shows results even if persistence fails. Await the insert, handle the returnederror, and transition only after success.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/hooks/useTakeQuiz.ts` around lines 152 - 160, Update the auto-submit persistence branch in useTakeQuiz to await the supabase.from("quiz_attempts").insert operation, inspect its returned error, and handle failures through the existing catch path. Only clear the deadline and advance to the results state after the insert succeeds.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/hooks/useQuizDetail.ts`:
- Around line 62-65: Add the same enabled: !!quizId guard used by questionsQuery
and attemptsQuery to the quizQuery options, ensuring fetchQuiz does not run
until quizId is truthy.
- Around line 171-174: Update the saving value in useQuizDetail to include
deleteQuizMutation.isPending alongside the existing mutation pending states, so
it remains true while quiz deletion is in progress.
- Around line 159-163: Update the error computation in useQuizDetail so it also
reports failures from questionsQuery and attemptsQuery, rather than only
quizQuery.error. Preserve the existing null result when all three queries
succeed, and use the first available query error’s message consistently.
In `@src/hooks/useQuizzes.ts`:
- Around line 48-63: Update deleteQuiz to invalidate the ["stats"] query after a
successful quiz deletion, alongside the existing ["quizzes"] cache update,
matching useQuizDetail's deleteQuizMutation behavior.
In `@src/hooks/useRating.ts`:
- Around line 54-62: Update the onSuccess handler in the useMutation
configuration within useRating to also invalidate the quiz-detail query keyed by
["quiz", quizId], while preserving the existing rating cache update and quizBank
invalidation.
In `@src/hooks/useStats.ts`:
- Around line 10-49: Remove the separate quizCount query from fetchStats and use
userQuizzes.length for totalQuizzes, while preserving the existing quizIds,
question, and attempt counting behavior.
In `@src/hooks/useTakeQuiz.ts`:
- Around line 138-150: Update the deadline initialization in useTakeQuiz so a
new deadline is created only when deadline is absent, not when it is expired.
Preserve the existing expired deadline and its storage value, allowing the
remaining <= 0 branch to set the timer to zero and trigger auto-submit.
- Around line 79-95: Update the useTakeQuiz query destructuring to capture
questionsError from the questions useQuery, then include it when deriving the
returned error alongside quizError. Preserve the existing loading behavior and
message extraction, while ensuring either query failure produces a non-null
error.
- Around line 124-129: Replace the global boolean guard in useTakeQuiz with
tracking for the initialized quiz ID, and update the initialization check to
allow setup when the current quizId differs. Store the current quizId after
initializing so switching quizzes reinitializes the deadline and timer while
preventing duplicate initialization for the same quiz.
---
Outside diff comments:
In `@src/hooks/useTakeQuiz.ts`:
- Around line 152-160: Update the auto-submit persistence branch in useTakeQuiz
to await the supabase.from("quiz_attempts").insert operation, inspect its
returned error, and handle failures through the existing catch path. Only clear
the deadline and advance to the results state after the insert succeeds.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 87ace30d-16fa-4295-bf01-ed72e56ead90
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (10)
app/layout.tsxdocs/PREP_REACT_QUERY_PRT.mdpackage.jsonsrc/components/ReactQueryProvider.tsxsrc/hooks/useQuizBank.tssrc/hooks/useQuizDetail.tssrc/hooks/useQuizzes.tssrc/hooks/useRating.tssrc/hooks/useStats.tssrc/hooks/useTakeQuiz.ts
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: Mistura <101476258+Truella@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/hooks/useStats.ts (2)
47-55: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winScope the stats cache by user.
useQueryuses the shared key["stats"], butfetchStats()reads the authenticated user at runtime. Include the user ID in the key or reset this query on auth changes so a new session can’t reuse the previous account’s cached totals.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/hooks/useStats.ts` around lines 47 - 55, Update useAnalyticsStats to include the authenticated user ID in the useQuery queryKey, ensuring stats caches are isolated per account and a new session cannot reuse another user’s totals; keep fetchStats and the existing loading/default behavior unchanged.
10-17: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winThrow Supabase errors and scope the stats cache by user. The auth, quiz, question, and attempt queries currently collapse failures into zero stats, so React Query treats them as success and won’t retry.
queryKey: ["stats"]is also shared across sessions, andselect("id")without pagination can truncate at Supabase’s 1,000-row cap; includeuser.idin the key or clear it on auth changes, and page through quiz IDs.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/hooks/useStats.ts` around lines 10 - 17, Update fetchStats and its React Query configuration to propagate errors from the auth, quiz, question, and attempt Supabase queries instead of converting failures into zero values, while preserving the unauthenticated zero-stats result. Scope the stats queryKey by user.id (or clear the cache on auth changes), and paginate the quizzes query so all quiz IDs are processed beyond Supabase’s 1,000-row limit.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/hooks/useStats.ts`:
- Around line 14-20: Update the quiz-loading logic in useStats to avoid
Supabase’s 1,000-row limit before deriving quizIds and quizCount: either
paginate all matching quizzes or use a server-side aggregate/RPC that returns
the required quiz, question, and attempt counts. Ensure totalQuizzes,
totalQuestions, and totalAttempts remain accurate for users with more than 1,000
quizzes.
---
Outside diff comments:
In `@src/hooks/useStats.ts`:
- Around line 47-55: Update useAnalyticsStats to include the authenticated user
ID in the useQuery queryKey, ensuring stats caches are isolated per account and
a new session cannot reuse another user’s totals; keep fetchStats and the
existing loading/default behavior unchanged.
- Around line 10-17: Update fetchStats and its React Query configuration to
propagate errors from the auth, quiz, question, and attempt Supabase queries
instead of converting failures into zero values, while preserving the
unauthenticated zero-stats result. Scope the stats queryKey by user.id (or clear
the cache on auth changes), and paginate the quizzes query so all quiz IDs are
processed beyond Supabase’s 1,000-row limit.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 03ddd908-e1c6-4cb3-ab45-a1ababbd285d
📒 Files selected for processing (1)
src/hooks/useStats.ts
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
src/hooks/useRating.ts (2)
48-67: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winSurface rating fetch failures.
fetchRatingignores Supabase errors, so a failed load still resolves tonulland looks like “no rating.” Propagate the query error, then return it alongsidemutation.error, and add a fetch-failure test.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/hooks/useRating.ts` around lines 48 - 67, Update fetchRating and the useQuery flow in useRating so Supabase fetch errors are propagated instead of resolving as null. Return the query’s fetch error alongside any mutation error through the hook’s error field, and add a test covering a failed rating fetch and the resulting error.
16-42: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winRemove the client-side average recomputation.
The database trigger onquiz_ratingsalready keepsquizzes.average_ratingin sync; this extra select/update can race with concurrent ratings and overwrite the correct value with a stale average. Delete the post-upsert update.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/hooks/useRating.ts` around lines 16 - 42, Remove the post-upsert ratings select and average calculation from upsertRating; leave only the quiz_ratings upsert and its existing error handling, relying on the database trigger to maintain quizzes.average_rating.src/hooks/useTakeQuiz.ts (1)
165-176: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winPersist expired auto-submit before clearing state
src/hooks/useTakeQuiz.ts:165-176— this branch firesquiz_attempts.insert(...)without awaiting it and never writesquiz_results_${quizId}. Reuse the samesaveAttempt/localStorage path ashandleTimerExpire, and clear the deadline/set submitted state only after persistence succeeds.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/hooks/useTakeQuiz.ts` around lines 165 - 176, The expired auto-submit branch in useTakeQuiz must persist the attempt before clearing state. Reuse the existing saveAttempt and quiz_results_${quizId} localStorage flow from handleTimerExpire, await persistence, and only then call clearDeadline, setShowResults, and setIsSubmitted; remove the unawaited direct supabase insert and empty catch.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/hooks/useQuizBank.test.ts`:
- Around line 7-8: Name the QueryClientProvider wrapper returned by the test
helper to satisfy the react/display-name rule. Update the anonymous component
around QueryClientProvider by defining a named wrapper or assigning its
displayName before returning it, while preserving the existing queryClient and
children behavior.
In `@src/hooks/useRating.test.ts`:
- Around line 7-8: In the QueryClientProvider wrapper returned by the test
setup, replace the anonymous component with a named wrapper or assign it an
explicit displayName before returning it, while preserving the existing
queryClient and children behavior.
In `@src/hooks/useTakeQuiz.test.ts`:
- Around line 5-8: Update createWrapper to return a named ReactQueryTestWrapper
component instead of an anonymous function, preserving the existing
QueryClientProvider setup and retry configuration.
---
Outside diff comments:
In `@src/hooks/useRating.ts`:
- Around line 48-67: Update fetchRating and the useQuery flow in useRating so
Supabase fetch errors are propagated instead of resolving as null. Return the
query’s fetch error alongside any mutation error through the hook’s error field,
and add a test covering a failed rating fetch and the resulting error.
- Around line 16-42: Remove the post-upsert ratings select and average
calculation from upsertRating; leave only the quiz_ratings upsert and its
existing error handling, relying on the database trigger to maintain
quizzes.average_rating.
In `@src/hooks/useTakeQuiz.ts`:
- Around line 165-176: The expired auto-submit branch in useTakeQuiz must
persist the attempt before clearing state. Reuse the existing saveAttempt and
quiz_results_${quizId} localStorage flow from handleTimerExpire, await
persistence, and only then call clearDeadline, setShowResults, and
setIsSubmitted; remove the unawaited direct supabase insert and empty catch.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: af2de30f-45ab-47f2-9629-b13c738f31f9
📒 Files selected for processing (5)
src/hooks/useQuizBank.test.tssrc/hooks/useRating.test.tssrc/hooks/useRating.tssrc/hooks/useTakeQuiz.test.tssrc/hooks/useTakeQuiz.ts
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/hooks/useTakeQuiz.ts (2)
203-226: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winClaim the submission guard before starting expiry persistence.
This path starts
saveAttemptwhilesubmittedRef.currentremainsfalse. A timer callback or manual confirmation during the pending insert can pass the same guard and create duplicatequiz_attemptsrows. Set the guard before the async call; reset it and surface an error only if persistence fails.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/hooks/useTakeQuiz.ts` around lines 203 - 226, Update the expiry handler in useTakeQuiz around saveAttempt to claim submittedRef.current before starting the asynchronous persistence, preventing timer or manual-submit callbacks from entering concurrently. If saveAttempt fails, reset the guard and surface the persistence error; otherwise preserve the existing result-display and localStorage flow.
173-210: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winRestore saved progress before scoring an expired quiz.
The expiry effect runs before the restoration effect at Lines 239-268. On reload after expiry,
saveAttemptcaptures the initial emptyselectedAnswers, so persisted in-progress answers are replaced by a zero-score auto-submission. Gate deadline initialization/auto-submit on completion of progress restoration, and add a regression test with saved answers plus an expired deadline.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/hooks/useTakeQuiz.ts` around lines 173 - 210, Update the initialization effect around initializedQuizIdRef and its expired-deadline auto-submit so it waits for the progress-restoration flow to complete before calculating or saving the attempt. Ensure restored selectedAnswers are used when saveAttempt runs for an already expired quiz, while preserving normal deadline initialization and auto-submit behavior; add a regression test covering saved answers with an expired deadline.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/hooks/useStats.ts`:
- Around line 43-51: Move the question and attempt count aggregation out of the
client-side quizIds filter in the stats-loading flow of useStats: replace both
.in("quiz_id", quizIds) queries with a server-side RPC or view that scopes
results by the current user's created_by value. Preserve the existing count
results and Promise.all behavior without serializing the full quiz ID list into
the request.
In `@src/hooks/useTakeQuiz.ts`:
- Around line 208-226: Update the async submission flow in useTakeQuiz so the
effect creates a cancellation flag tied to the current quizId and returns
cleanup that marks it inactive. After saveAttempt(elapsedTotal) resolves, verify
the effect is still active and quizId remains current before calling
clearDeadline, setShowResults, setIsSubmitted, or saving quiz results; ensure
the async callback does not update state for an older quiz.
- Around line 159-167: Update the quiz-attempt persistence flow in useTakeQuiz
around the supabase.from("quiz_attempts").insert call to capture and inspect its
returned error before returning true. On database failure, propagate or handle
the error through the existing catch path so deadline clearing and result
display only occur after a successful insert.
---
Outside diff comments:
In `@src/hooks/useTakeQuiz.ts`:
- Around line 203-226: Update the expiry handler in useTakeQuiz around
saveAttempt to claim submittedRef.current before starting the asynchronous
persistence, preventing timer or manual-submit callbacks from entering
concurrently. If saveAttempt fails, reset the guard and surface the persistence
error; otherwise preserve the existing result-display and localStorage flow.
- Around line 173-210: Update the initialization effect around
initializedQuizIdRef and its expired-deadline auto-submit so it waits for the
progress-restoration flow to complete before calculating or saving the attempt.
Ensure restored selectedAnswers are used when saveAttempt runs for an already
expired quiz, while preserving normal deadline initialization and auto-submit
behavior; add a regression test covering saved answers with an expired deadline.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: eb8f0bf4-1713-4b73-a15d-18e244592084
📒 Files selected for processing (8)
src/hooks/useQuizBank.test.tssrc/hooks/useQuizDetail.tssrc/hooks/useQuizzes.tssrc/hooks/useRating.test.tssrc/hooks/useRating.tssrc/hooks/useStats.tssrc/hooks/useTakeQuiz.test.tssrc/hooks/useTakeQuiz.ts
…d score calculation
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/hooks/useStats.ts`:
- Around line 16-33: Update fetchStats to accept and use the query’s user.id
instead of independently calling supabase.auth.getUser(), then pass that ID
through the stats query configuration. Add enabled: !!user to prevent execution
without an authenticated user while preserving the existing query key.
In `@src/hooks/useTakeQuiz.test.ts`:
- Around line 266-288: Extend the expired-deadline test around useTakeQuiz to
inspect the quiz_attempts insert call and assert its payload preserves the saved
answer { 0: 3 } instead of submitting an empty answers object. Keep the existing
UI assertions, and target the mocked Supabase insert invocation after
auto-submission completes.
In `@src/hooks/useTakeQuiz.ts`:
- Around line 213-220: Update the submission flow in useTakeQuiz around
readSavedResults and saveAttempt to hydrate completion state before inserting an
attempt: skip saveAttempt when completed quiz_results already exist, and
otherwise restore answers from quiz_progress when no completed results are
available. Ensure the restored progress answers, rather than an empty fallback,
are passed to setSelectedAnswers and saveAttempt.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: ea59ac84-4c47-42b3-903b-86390eb8c3b1
📒 Files selected for processing (3)
src/hooks/useStats.tssrc/hooks/useTakeQuiz.test.tssrc/hooks/useTakeQuiz.ts
… improved answer handling
Summary
Migrates all data-fetching hooks to React Query (
@tanstack/react-query). Benefits: automatic caching (navigating back to My Quizzes is instant), request deduplication, background refetching on window focus, and 30-second polling on stats and quiz attempt counts so creators see updated numbers without refreshing. Mutations inuseQuizDetailnow invalidate related queries automatically instead of manually patching local state. No UI changes — purely the data layer.New dependency:
@tanstack/react-queryChanged files
package.jsonsrc/components/ReactQueryProvider.tsx(new)app/layout.tsxsrc/hooks/useQuizzes.tssrc/hooks/useStats.tssrc/hooks/useQuizBank.tssrc/hooks/useQuizDetail.tssrc/hooks/useRating.tssrc/hooks/useTakeQuiz.tsChecklist
npm install @tanstack/react-querycompletednpx tsc --noEmitpassesnpx next lintpasses with zero warningsMy Quizzes loads — navigating away and back is instant (cached)
Dashboard stats poll every 30 seconds
Quiz detail attempt count updates without refresh
Quiz Bank filter changes trigger new fetch
Taking a quiz still works end-to-end
Rating submission still works
Branch up to date with
mainNo
console.logstatementsSummary by CodeRabbit