Update Server Component after data has been changed by Client Component in Next.js

The only way to update a Server Component is to reload the page. As it’s sent to the browser as static HTML without any JavaScript attached to it to have interactivity.

To reload the page while keeping client side states, you could use router.refresh(), where router is the returned value by useRouter(). Here is an example working with a Todo List application:

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("https://api.example.com/todos", { cache: 'no-store' });
  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>
  );
}

⚠️: refresh() could re-produce the same result if fetch requests are cached. This is why that cache: 'no-store' on this example.

Leave a Comment

Hata!: SQLSTATE[HY000] [1045] Access denied for user 'divattrend_liink'@'localhost' (using password: YES)