Server Functions
Call server-side logic directly from components with RPC-like functions. Dinou uniquely allows Server Functions to return rendered Components, not just JSON data.
Server Functions ("use server")
Define functions with the "use server" directive to execute server-side logic directly from your components. Unlike traditional RPC, these functions can return fully rendered React Components.
// src/server-functions/get-post.jsx
"use server";
import db from "./db";
import Post from "@/components/post.jsx";
export async function getPost(postId) {
const data = await db.query("SELECT * FROM posts WHERE id = ?", [postId]);
// 🪄 Returns a rendered Component, not just JSON
return <Post post={data} />;
}Server Functions always execute on the server, even when called from Client Components. This keeps sensitive logic and database credentials secure.
Suspense Integration
Dinou provides built-in integration with react-enhanced-suspense to handle loading fallbacks and data-fetching states smoothly when calling Server Functions.
1. In Server Components (Direct Promise)
Inside Server Components, you can call the Server Function directly and pass the returned promise as the child of the Suspense component. Since react-enhanced-suspense behaves identically to React's native Suspense when it is used exactly like it—meaning, without any props besides children and fallback, and without children being a function—it will suspend and render the component once the promise resolves.
// src/post-section/page.jsx
// Server Component
import Suspense from "react-enhanced-suspense";
import { getPost } from "@/server-functions/get-post";
export default function Page() {
return (
<section>
<h1>Latest Post</h1>
{/* Pass the promise directly as a child */}
<Suspense fallback={<p>Loading post on the server...</p>}>
{getPost("post-1")}
</Suspense>
</section>
);
}2. In Client Components (with resourceId)
Inside Client Components, to prevent component re-execution loops and allow interactive state updates or refreshes, you must provide a unique resourceId prop to Suspense and pass the function call wrapped inside a callback function as its child.
// src/post-viewer/page.jsx
"use client";
import { useState } from "react";
import Suspense from "react-enhanced-suspense";
import { getPost } from "@/server-functions/get-post";
export default function Page() {
const [postId, setPostId] = useState("post-1");
return (
<div>
<button onClick={() => setPostId("post-2")}>Load Next Post</button>
{/* Wrap function call in a callback and specify resourceId */}
<Suspense
fallback={<p>Loading post on the client...</p>}
resourceId={`post-viewer-${postId}`}
>
{() => getPost(postId)}
</Suspense>
</div>
);
}Server Actions (Form Mutations)
Server Functions can also be used as Server Actions by passing them to the action prop of a <form>. This allows you to handle form submissions and data mutations directly on the server without creating API endpoints manually.
The function receives a FormData object containing input values automatically.
Forms work natively even before JavaScript loads on the client.
Use getContext to redirect the user after a successful mutation.
1. Define the Action
Create a Server Function that extracts data from FormData and performs the mutation.
// src/actions/create-post.js
"use server";
import { getContext } from "dinou";
import { addPost } from "@/db/posts.js";
export async function createPost(formData) {
const context = getContext();
// 1. Extract data
const title = formData.get("title");
const content = formData.get("content");
// 2. Mutate (Save to DB)
await addPost({ title, content });
// 3. Redirect
context?.res?.redirect("/posts");
}2. Use in Client Components
Pass the function to the form action. You can use the new React 19 useFormStatus hook to show pending states.
// src/new-post/page.jsx
"use client";
import { useFormStatus } from "react-dom";
import { createPost } from "@/actions/create-post";
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending} className="btn-primary">
{pending ? "Saving..." : "Create Post"}
</button>
);
}
export default function Page() {
return (
<form action={createPost} className="flex flex-col gap-4">
{/* The 'name' attribute is required for FormData extraction */}
<input name="title" placeholder="Title" required className="input" />
<textarea name="content" placeholder="Content" required className="textarea" />
<SubmitButton />
</form>
);
}3. Use in Server Componentsv5.0.3+
Dinou introduces full native support for using Server Actions inside Server Components (without the "use client" directive). This provides progressive enhancement out of the box—the form submits and processes the action on the server even if JavaScript is disabled in the browser.
// src/new-post-server/page.jsx
// Note: This is a Server Component (no "use client" directive)
import { createPost } from "@/actions/create-post";
export default function Page() {
return (
<form action={createPost} className="flex flex-col gap-4">
<input name="title" placeholder="Title" required className="input" />
<textarea name="content" placeholder="Content" required className="textarea" />
<button type="submit" className="btn-primary">
Create Post
</button>
</form>
);
}