I am still trying to wrap my head around this scenario. Can anyone please suggest what is the correct way to do this in Next.js 13?
I diplay a list of users in a Server Component, for example, like this (using MongoDB):
// UsersList.jsx
const UsersList = () => {
const users = await usersCollection.getUsers()
return (
<div>
{users.map(user) => <div>{user}</div>}
</div>
)
}
And on the same page, I have also defined client component for adding users:
// UsersEdit.jsx
'use client'
const UsersEdit = () => {
const handleAdd() => // calls POST to /api/users
return // render input button
}
Both are displayed together like this in a Server Component Page:
// page.jsx
const Users = () => {
return (
<div>
<UsersList />
<UsersEdit />
</div>
)
}
How should I "reload" or "notify" UsersList
that a new user has been added to the collection to force it to display a new user/updated user?
CodePudding user response:
For now, the only way to have the updated data by your Client Component reflected on the Server Component is to call router.refresh()
, where router
is the returned value by useRouter
, after your request to the API. As you can read on the official Next.js doc:
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:
You can mutate data inside the app directory with
router.refresh()
.
And they gave a wonderful example, working with a Todo List application. I added it below to have a more complete thread.
Let's consider a list view. Inside your Server Component, you fetch the list of items:
// app/page.tsx
import Todo from "./todo";
async function getTodos() {
const res = await fetch("/api/todos");
const todos = 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.
// app/todo.tsx
"use client";
import { useRouter } from 'next/navigation';
import { useState, useTransition } from 'react';
export default function 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>
);
}