The Next.js team is working on a new RFC for mutating data in Next.js. This RFC has not been published yet. For now, we recommend the following pattern:
After a data mutation, you can use router.refresh()
to refresh (fetch updated data and re-render on the server) the current route from the root layout down.
Let's consider a list view. Inside your Server Component, you fetch the list of items:
import Todo from './todo';
interface Todo {
id: number;
title: string;
completed: boolean;
}
async function getTodos() {
const res = await fetch('https://api.example.com/todos');
const todos: Todo[] = await res.json();
return todos;
}
export default async function Page() {
const todos = await getTodos();
return (
<ul>
{todos.map((todo) => (
<Todo key={todo.id} {...todo} />
))}
</ul>
);
}
Each item has its own Client Component. This allows the component to use event handlers (like onClick
or onSubmit
) to trigger a mutation.
"use client";
import { useRouter } from 'next/navigation';
import { useState, useTransition } from 'react';
interface Todo {
id: number;
title: string;
completed: boolean;
}
export default function Todo(todo: Todo) {
const router = useRouter();
const [isPending, startTransition] = useTransition();
const [isFetching, setIsFetching] = useState(false);
// Create inline loading UI
const isMutating = isFetching || isPending;
async function handleChange() {
setIsFetching(true);
// Mutate external data source
await fetch(`https://api.example.com/todo/${todo.id}`, {
method: 'PUT',
body: JSON.stringify({ completed: !todo.completed }),
});
setIsFetching(false);
startTransition(() => {
// Refresh the current route and fetch new data from the server without
// losing client-side browser or React state.
router.refresh();
});
}
return (
<li style={{ opacity: !isMutating ? 1 : 0.7 }}>
<input
type="checkbox"
checked={todo.completed}
onChange={handleChange}
disabled={isPending}
/>
{todo.title}
</li>
);
}
By calling router.refresh()
, the current route will refresh and fetch an updated list of todos from the server. This does not affect browser history, but it does refresh data from the root layout down. When using refresh()
, client-side state is not lost, including both React and browser state.
We can use the useTransition
hook to create inline loading UI. We wrap the mutation in a startTransition
function to mark the update as a transition and use the isPending
flag to show loading UI while the transition is pending.