Advanced Patterns: The "Dinou Pattern"
Combine Server Functions, Global State, and Enhanced Suspense for granular reactivity. Handle mutations and list updates without full page reloads.
The Concept
The Dinou Pattern leverages the unique ability of Server Functions to return Client Components. After a mutation, we return a "Headless State Updater" component that:
- Updates global state when it mounts
- Triggers re-fetching of dependent data through
resourceIdchanges
Architecture Diagram
Client triggers Server Function
Server performs DB operation
Returns Headless Updater Component
Updater changes global state on mount
resourceId changes trigger re-fetchImplementation
Follow these steps to implement the Dinou Pattern for a todo list with add functionality.
1. The Global Store (Atoms)
Define atoms for state management using jotai-wrapper (or any other Global State Management library).
// src/atoms.js
import { atom } from "jotai";
import getAPIFromAtoms from "jotai-wrapper";
export const { useAtom, useSetAtom, useAtomValue, getAtom, selectAtom } =
getAPIFromAtoms({
tasksListKey: atom(0), // Cache buster for tasks list
isAddTask: atom(false), // Flag to trigger add task mutation
// Add other atoms as needed...
});2. The Headless Updater (Client Component)
A component that renders nothing but updates global state when mounted.
// src/components/add-task-updater.jsx
"use client";
import { useEffect } from "react";
import { useSetAtom } from "@/atoms";
export default function AddTaskUpdater() {
const setTasksListKey = useSetAtom("tasksListKey");
const setIsAddTask = useSetAtom("isAddTask");
useEffect(() => {
// Update the key to force re-fetch of tasks list
setTasksListKey((k) => k + 1);
// Reset the add task flag
setIsAddTask(false);
}, [setTasksListKey, setIsAddTask]);
return null; // Renders nothing visually
}3. The Server Function (Mutation)
Performs the database operation and returns the Headless Updater.
// src/server-functions/add-task.jsx
"use server";
import AddTaskUpdater from "../components/add-task-updater";
import { tasks } from "./db";
export async function addTask(text) {
// Perform database mutation
tasks.push(text);
// 🪄 Magic: Return the updater to run client-side logic
return <AddTaskUpdater />;
}4. Data Fetching Server Function
Fetches the current list of tasks.
// src/server-functions/tasks-list.jsx
"use server";
import { tasks } from "./db";
import TasksListDisplay from "../components/tasks-list-display";
export async function tasksList() {
return <TasksListDisplay tasks={tasks} />;
}5. Display Component (Client Component)
Renders the list of tasks.
// src/components/tasks-list-display.jsx
"use client";
export default function TasksListDisplay({ tasks }) {
return (
<div>
{tasks.map((t, index) => (
<div key={index}>{t}</div>
))}
</div>
);
}6. The Page (Putting It All Together)
// src/page.jsx
"use client";
import Suspense from "react-enhanced-suspense";
import { useAtomValue, useAtom } from "@/atoms";
import { addTask } from "./server-functions/add-task";
import { tasksList } from "./server-functions/tasks-list";
import { useState } from "react";
export default function Page() {
const tasksListKey = useAtomValue("tasksListKey");
const [isAddTask, setIsAddTask] = useAtom("isAddTask");
const [text, setText] = useState("");
return (
<div>
{/* Mutation Form */}
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
/>
<button onClick={() => setIsAddTask(true)}>
Add Task
</button>
{/* Conditional mutation suspense */}
{isAddTask && (
<Suspense fallback="Adding task..." resourceId="add-task">
{() => addTask(text)}
</Suspense>
)}
{/* Reactive List */}
<Suspense
fallback={<div>Loading tasks...</div>}
resourceId={`tasks-list-${tasksListKey}`}
>
{() => tasksList()}
</Suspense>
</div>
);
}How It Works
- User clicks "Add Task", setting
isAddTasktotrue - Conditional
Suspenserenders and callsaddTask() - Server adds task to DB, returns
AddTaskUpdater AddTaskUpdatermounts, incrementstasksListKey- Changed
resourceIdtriggers re-fetch oftasksList() - Updated list streams to the UI
Granular Reactivity
Only affected data re-fetches. Other parts of the page remain unchanged and performant.
Loose Coupling
Server Functions, State, and UI are decoupled. Easy to test, refactor, and maintain.
