Skip to content

feat(examples): add Next.js App Router optimistic updates example#10997

Open
guirab wants to merge 3 commits into
TanStack:mainfrom
guirab:feat/nextjs-app-optimistic-updates
Open

feat(examples): add Next.js App Router optimistic updates example#10997
guirab wants to merge 3 commits into
TanStack:mainfrom
guirab:feat/nextjs-app-optimistic-updates

Conversation

@guirab

@guirab guirab commented Jun 28, 2026

Copy link
Copy Markdown

🎯 Changes

Adds a new example demonstrating optimistic updates with Next.js App Router (App Directory).

This example was missing from the repo — the existing Next.js examples only cover prefetching
and streaming. Optimistic updates with App Router is a common real-world pattern that many
users ask about in discussions.

The example shows both approaches supported by TanStack Query v5:

  1. Via UI variables (TodoListUI.tsx) — renders the pending item directly from
    mutation.variables, simpler and requires no cache manipulation or rollback logic.

  2. Via cache manipulation (TodoListCache.tsx) — uses onMutate to update the cache
    optimistically, with typed context and automatic rollback via onError if the mutation fails.

A tab switcher lets users compare both approaches side by side.

The API route simulates ~30% random failure to demonstrate the rollback behavior.
Implementation follows the conventions of nextjs-app-prefetching.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm run test:pr.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Summary by CodeRabbit

  • New Features

    • Added a new Next.js todo example showcasing two optimistic update approaches.
    • Introduced a tabbed interface to switch between update behaviors.
    • Added a live todo list with add, loading, and rollback states.
    • Included app setup for shared query state, hydration, and developer tooling.
  • Bug Fixes

    • Improved error handling for failed todo submissions and invalid input.
    • Added automatic refresh after updates so the list stays in sync.
  • Documentation

    • Added setup and usage guidance for the new example.

@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0ae1c6bb-24f1-457a-805e-995ec1ed9a6c

📥 Commits

Reviewing files that changed from the base of the PR and between 7ad24c1 and 083bcda.

📒 Files selected for processing (1)
  • examples/react/nextjs-app-optimistic-updates/app/api/todos/route.ts

📝 Walkthrough

Walkthrough

Adds a new examples/react/nextjs-app-optimistic-updates project: an in-memory todos API route with randomized failures, a server-prefetched Next.js page with React Query hydration, and two client components demonstrating optimistic updates via mutation.variables and via cache manipulation with rollback.

Changes

Next.js App Router Optimistic Updates Example

Layer / File(s) Summary
Project config and tooling
package.json, tsconfig.json, next.config.js, .gitignore
Adds package metadata with TanStack Query dependencies, strict TypeScript config, empty Next.js config, and standard ignore patterns.
In-memory todos API
app/api/todos/data.ts, app/api/todos/route.ts
Defines the Todo interface, an in-memory store, getTodos() with 200ms delay, and GET/POST route handlers. POST validates input, waits 500ms, randomly returns HTTP 500 ~30% of the time, and appends a new todo on success.
Query client and app shell
app/get-query-client.ts, app/providers.tsx, app/layout.tsx
Adds makeQueryClient() with 60s staleTime and pending-query dehydration, a singleton browser client accessor, a client-side Providers wrapping QueryClientProvider and devtools, and the root layout.
Server page with hydration
app/page.tsx
Creates the Home page that prefetches ['todos'] on the server and renders ApproachTabs inside a HydrationBoundary with the dehydrated state.
Tabs and mutation.variables UI
components/ApproachTabs.tsx, components/TodoListUI.tsx
Adds a tab switcher toggling between two approaches, and TodoListUI which shows a pending item from mutation.variables during flight and invalidates ['todos'] on settle.
Cache-manipulation todo list
components/TodoListCache.tsx
Adds TodoListCache with onMutate that cancels queries, snapshots cache, and writes an optimistic entry; onError restores the snapshot; onSettled invalidates. Optimistic items render with reduced opacity and a "(saving…)" suffix.
README
README.md
Documents both optimistic update approaches, the random failure behavior, and run instructions.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Suggested reviewers

  • TkDodo

Poem

🐇 Hopping through the cache with glee,
Optimistic todos, saved or free!
If the server fails (thirty percent!),
We roll back fast — no time is spent.
Two approaches shown, side by side,
In Next.js App Router we confide. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding a Next.js App Router optimistic updates example.
Description check ✅ Passed The description follows the required template and includes changes, checklist, and release impact sections.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@socket-security

socket-security Bot commented Jun 28, 2026

Copy link
Copy Markdown

No dependency changes detected. Learn more about Socket for GitHub.

👍 No dependency changes detected in pull request

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 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 `@examples/react/nextjs-app-optimistic-updates/.eslintrc.cjs`:
- Around line 1-9: The current ESLint config only enables React rules, so the
example’s TypeScript `.tsx` files may fail to parse before linting runs. Update
the configuration in the `.eslintrc.cjs` for the Next.js optimistic updates
example by extending the Next.js TypeScript preset, using `next/core-web-vitals`
and `next/typescript` (or equivalent TypeScript parser setup) alongside the
existing React support. Make sure the config still covers the example’s React
and hooks rules while correctly handling TypeScript sources.

In `@examples/react/nextjs-app-optimistic-updates/app/api/todos/route.ts`:
- Around line 24-44: The POST handler in the todos route accepts unvalidated
input, so invalid or whitespace-only text can be pushed into the shared todos
array. Update POST to validate the parsed request body before creating newTodo,
using the existing request/json flow to reject missing, non-string, or
trimmed-empty text. Return a 400 response for invalid input and only call
todos.push after the text has been sanitized and confirmed valid.
- Around line 9-18: Move the runtime data and helper out of the route handler
module: `todos` and `getTodos` in `app/api/todos/route.ts` should be relocated
to a sibling module and imported where needed. Keep `route.ts` limited to HTTP
handlers/config exports only, and reference the shared module from the route
handler so Next’s route export rules are satisfied.

In `@examples/react/nextjs-app-optimistic-updates/components/TodoListCache.tsx`:
- Around line 42-65: The optimistic update flow in TodoListCache’s
onMutate/onError leaves the optimistic todo visible when previousTodos is
undefined, so track the created optimistic item’s id in the mutation context and
use it in onError to remove that specific row directly when there is no
snapshot. Keep the existing rollback to queryClient.setQueryData for the
previousTodos path, but in the no-snapshot path ensure the optimistic entry
added by queryClient.setQueryData is deleted immediately rather than waiting for
the refetch.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9ed6d652-181d-48d6-86e8-8c6367564728

📥 Commits

Reviewing files that changed from the base of the PR and between 610e8d1 and 00a4652.

📒 Files selected for processing (14)
  • examples/react/nextjs-app-optimistic-updates/.eslintrc.cjs
  • examples/react/nextjs-app-optimistic-updates/.gitignore
  • examples/react/nextjs-app-optimistic-updates/README.md
  • examples/react/nextjs-app-optimistic-updates/app/api/todos/route.ts
  • examples/react/nextjs-app-optimistic-updates/app/get-query-client.ts
  • examples/react/nextjs-app-optimistic-updates/app/layout.tsx
  • examples/react/nextjs-app-optimistic-updates/app/page.tsx
  • examples/react/nextjs-app-optimistic-updates/app/providers.tsx
  • examples/react/nextjs-app-optimistic-updates/components/ApproachTabs.tsx
  • examples/react/nextjs-app-optimistic-updates/components/TodoListCache.tsx
  • examples/react/nextjs-app-optimistic-updates/components/TodoListUI.tsx
  • examples/react/nextjs-app-optimistic-updates/next.config.js
  • examples/react/nextjs-app-optimistic-updates/package.json
  • examples/react/nextjs-app-optimistic-updates/tsconfig.json

Comment on lines +1 to +9
/** @type {import('eslint').Linter.Config} */
module.exports = {
extends: ['plugin:react/jsx-runtime', 'plugin:react-hooks/recommended'],
settings: {
react: {
version: 'detect',
},
},
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Use the Next TypeScript ESLint preset here examples/react/nextjs-app-optimistic-updates/.eslintrc.cjs only enables React rules, so the example’s .tsx files can hit parse errors before any lint rules run. Extend next/core-web-vitals + next/typescript (or add @typescript-eslint/parser) so the TypeScript sources are linted.

🤖 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 `@examples/react/nextjs-app-optimistic-updates/.eslintrc.cjs` around lines 1 -
9, The current ESLint config only enables React rules, so the example’s
TypeScript `.tsx` files may fail to parse before linting runs. Update the
configuration in the `.eslintrc.cjs` for the Next.js optimistic updates example
by extending the Next.js TypeScript preset, using `next/core-web-vitals` and
`next/typescript` (or equivalent TypeScript parser setup) alongside the existing
React support. Make sure the config still covers the example’s React and hooks
rules while correctly handling TypeScript sources.

Comment thread examples/react/nextjs-app-optimistic-updates/app/api/todos/route.ts Outdated
Comment thread examples/react/nextjs-app-optimistic-updates/app/api/todos/route.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
examples/react/nextjs-app-optimistic-updates/app/api/todos/route.ts (1)

8-20: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Handle malformed JSON bodies explicitly.

request.json() can throw before the text validation runs, so an invalid POST still turns into a 500 instead of the 400 { error } contract your client code expects.

Suggested fix
 export async function POST(request: Request) {
-  const body = (await request.json()) as unknown
+  let body: unknown
+  try {
+    body = await request.json()
+  } catch {
+    return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })
+  }
 
   const text =
     body !== null &&
🤖 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 `@examples/react/nextjs-app-optimistic-updates/app/api/todos/route.ts` around
lines 8 - 20, The POST handler currently assumes request.json() always succeeds,
so malformed JSON can escape the text validation and return a 500 instead of the
expected 400 response. Update POST in the todos route to catch JSON parsing
failures around request.json(), and return the same NextResponse.json({ error:
'text is required' }, { status: 400 }) contract when the body cannot be parsed
or is invalid, keeping the existing text extraction/validation logic intact.
🤖 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.

Duplicate comments:
In `@examples/react/nextjs-app-optimistic-updates/app/api/todos/route.ts`:
- Around line 8-20: The POST handler currently assumes request.json() always
succeeds, so malformed JSON can escape the text validation and return a 500
instead of the expected 400 response. Update POST in the todos route to catch
JSON parsing failures around request.json(), and return the same
NextResponse.json({ error: 'text is required' }, { status: 400 }) contract when
the body cannot be parsed or is invalid, keeping the existing text
extraction/validation logic intact.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: db5f2199-d0de-4a40-a664-fc0e657b95e0

📥 Commits

Reviewing files that changed from the base of the PR and between 00a4652 and 7ad24c1.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (5)
  • examples/react/nextjs-app-optimistic-updates/app/api/todos/data.ts
  • examples/react/nextjs-app-optimistic-updates/app/api/todos/route.ts
  • examples/react/nextjs-app-optimistic-updates/app/page.tsx
  • examples/react/nextjs-app-optimistic-updates/components/TodoListCache.tsx
  • examples/react/nextjs-app-optimistic-updates/components/TodoListUI.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • examples/react/nextjs-app-optimistic-updates/app/page.tsx
  • examples/react/nextjs-app-optimistic-updates/components/TodoListCache.tsx
  • examples/react/nextjs-app-optimistic-updates/components/TodoListUI.tsx

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant