Home > Mobile >  Update Server Component after data has changed by Client Component in Next.js
Update Server Component after data has changed by Client Component in Next.js

Time:01-16

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>
  );
}
  • Related