Advanced Patterns: The "Dinou Pattern"
Combine Server Functions, Headless Client Updaters, and pure React 19 primitives (useSyncExternalStore, use(), and <Suspense>) for surgical reactivity — with zero external dependencies required (and full compatibility with external state managers).
The Concept
The Dinou Pattern leverages the unique ability of Server Functions to return Client Components over Flight RPC. After executing a mutation in Node.js, the server returns an invisible Headless State Updater component that:
- Mounts on the client (rendering
null, emitting 0 DOM nodes). - Executes a client-side
useEffectto update a native store based onuseSyncExternalStore. - Selectively invalidates dependent queries, streaming the updated UI via React 19
<Suspense>anduse()without full-page reloads.
addPatternTask())<PatternTaskUpdater />useSyncExternalStore<Suspense> re-fetches and renders the updated listImplementation
Follow these steps to implement the Dinou Pattern using pure React 19 primitives with zero external state libraries — or optionally alongside your preferred global state manager.
1. The Native Store (React 19 useSyncExternalStore)
Create a lightweight singleton store with native React 19 primitives. No external state library is required; however, if your project already uses or benefits from a state management library (such as Zustand, Jotai, or Redux), the Dinou Pattern is completely compatible and you can update your existing store inside the Headless Updater just as easily.
// src/pattern-store.ts
import { useSyncExternalStore } from "react";
// Pure native React 19 global state (0 external dependencies)
let tasksListKey = 0;
const listeners = new Set<() => void>();
export const patternStore = {
getSnapshot: () => tasksListKey,
incrementTasksListKey: () => {
tasksListKey += 1;
listeners.forEach((listener) => listener());
},
subscribe: (listener: () => void) => {
listeners.add(listener);
return () => {
listeners.delete(listener);
};
},
};
export function useTasksListKey(): number {
return useSyncExternalStore(
patternStore.subscribe,
patternStore.getSnapshot,
patternStore.getSnapshot // For SSR compatibility
);
}Note on external state libraries: While native useSyncExternalStore provides surgical reactivity with zero extra packages in your bundle, you can freely plug in any state library of your choice (like a Zustand set() call or a Jotai atom) inside the updater without any friction.
2. The Headless Updater (Client Component)
An invisible Client Component that renders null (0 DOM elements), but synchronizes the store when mounted.
// src/components/pattern-updater.tsx
"use client";
import { useEffect } from "react";
import { patternStore } from "../pattern-store";
export default function PatternTaskUpdater({ id, taskText }: { id?: string; taskText?: string }) {
useEffect(() => {
// When mounted on client after Server Function completes,
// atomically updates the native React store
patternStore.incrementTasksListKey();
}, [id, taskText]);
return null; // Headless component: emits zero DOM nodes
}3. The Server Function (Mutation)
Performs the backend mutation in Node.js and returns the Headless Updater component over Flight RPC.
// src/server-functions/pattern-demo.tsx
"use server";
import type { ReactNode } from "react";
import PatternTaskUpdater from "../components/pattern-updater";
import { tasksDb } from "./db";
export async function addPatternTask(text: string): Promise<ReactNode> {
const trimmed = text.trim();
const id = Math.random().toString(36).substring(2, 9);
if (trimmed) {
tasksDb.unshift({
id,
text: trimmed,
createdAt: new Date().toLocaleTimeString(),
});
}
// The server orchestrates the client by returning the Headless Updater!
return <PatternTaskUpdater key={id} id={id} taskText={trimmed} />;
}4. Data Query Server Function
Fetches and renders the updated list directly on the server.
// src/server-functions/pattern-demo.tsx (continued)
"use server";
import type { ReactNode } from "react";
import PatternTasksList from "../components/pattern-tasks-list";
import { tasksDb } from "./db";
export async function fetchPatternTasks(): Promise<ReactNode> {
return <PatternTasksList tasks={[...tasksDb]} />;
}5. Display Component (Client Component)
Renders the list of tasks received from the server.
// src/components/pattern-tasks-list.tsx
"use client";
export interface PatternTaskItem {
id: string;
text: string;
createdAt: string;
}
export default function PatternTasksList({ tasks }: { tasks: PatternTaskItem[] }) {
if (!tasks || tasks.length === 0) {
return <p className="text-muted-foreground text-sm">No tasks yet.</p>;
}
return (
<ul className="space-y-2">
{tasks.map((task) => (
<li key={task.id} className="p-2.5 rounded-lg border bg-card flex justify-between">
<span>{task.text}</span>
<span className="text-xs text-muted-foreground">{task.createdAt}</span>
</li>
))}
</ul>
);
}6. The Page (Putting It All Together)
The view coordinates mutation promises and queries with React 19's native use() hook and <Suspense>.
// src/page.tsx
"use client";
import { useState, useEffect, Suspense, use, type ReactNode } from "react";
import { addPatternTask, fetchPatternTasks } from "./server-functions/pattern-demo";
import { useTasksListKey } from "./pattern-store";
function TasksStream({ promise }: { promise: Promise<ReactNode> }) {
// Native React 19: unroll the Flight stream promise
const content = use(promise);
return <>{content}</>;
}
function MutationStream({ promise }: { promise: Promise<ReactNode> }) {
const content = use(promise);
return <>{content}</>;
}
export default function Page() {
const tasksListKey = useTasksListKey();
const [text, setText] = useState("");
const [isMutating, setIsMutating] = useState(false);
const [mutationKey, setMutationKey] = useState(0);
const [mutationPromise, setMutationPromise] = useState<Promise<ReactNode> | null>(null);
const [tasksPromise, setTasksPromise] = useState<Promise<ReactNode>>(() => fetchPatternTasks());
// Invalidate and re-fetch only when the store increments tasksListKey
useEffect(() => {
if (tasksListKey > 0) {
setTasksPromise(fetchPatternTasks());
}
}, [tasksListKey]);
const handleAddTask = (e: React.FormEvent) => {
e.preventDefault();
const trimmed = text.trim();
if (!trimmed || isMutating) return;
setIsMutating(true);
setMutationKey((k) => k + 1);
const p = addPatternTask(trimmed);
p.finally(() => {
setIsMutating(false);
setText("");
});
setMutationPromise(p);
};
return (
<div className="space-y-6">
{/* 1. Mutation Form */}
<form onSubmit={handleAddTask} className="flex gap-2">
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="New task..."
disabled={isMutating}
className="input"
/>
<button type="submit" disabled={isMutating || !text.trim()} className="btn-primary">
{isMutating ? "Adding..." : "Add Task"}
</button>
</form>
{/* 2. Headless Mutation Mount (renders null, updates store) */}
{mutationPromise && (
<Suspense key={`mutation-${mutationKey}`} fallback={null}>
<MutationStream promise={mutationPromise} />
</Suspense>
)}
{/* 3. Surgical Reactive Tasks List */}
<Suspense
key={`pattern-tasks-${tasksListKey}`}
fallback={<div>Streaming updated list from Node.js...</div>}
>
<TasksStream promise={tasksPromise} />
</Suspense>
</div>
);
}How It Works
- User submits the form, initiating
addPatternTask()without full-page reloads. - The mutation promise is passed to a headless
<Suspense>boundary. - Server mutates the database in Node.js and returns
<PatternTaskUpdater />over Flight RPC. PatternTaskUpdatermounts, executing itsuseEffectto incrementtasksListKeyin the nativeuseSyncExternalStore.- The store notifies the page, calling
fetchPatternTasks()to obtain the fresh stream. - Only the tasks list
<Suspense>boundary updates smoothly, keeping inputs, state, and scroll position completely intact.
useSyncExternalStore, use(), <Suspense>, and Flight RPC. If your project already uses or benefits from an external state manager like Zustand or Jotai, they work seamlessly alongside it.