mirror of
https://github.com/reactjs/react.dev.git
synced 2026-02-24 20:53:08 +00:00
Move RSC stuff to docs
This commit is contained in:
@@ -255,11 +255,11 @@ TODO: we can't yet
|
||||
|
||||
</DeepDive>
|
||||
|
||||
## React Server Components (RSC) {/*new-feature-server-components*/}
|
||||
### React Server Components (RSC) {/*new-feature-server-components*/}
|
||||
|
||||
Server Components are a new option that allows rendering components ahead of time, before bundling, in an environment separate from your application (the "server"). They can run once at build time, or can be run for each request to a web server.
|
||||
Server Components are a new option that allows rendering components ahead of time, before bundling, in an environment separate from your application (the "server"). They can run once at build time, or can be run for each request to a web server.
|
||||
|
||||
Today we're releasing React Server Components as semver stable in React 19. This means libraries that ship Server Components and Server Actions can target React 19 as a peer dependency for use in frameworks that support the [Full-stack React Architecture](/learn/start-a-new-react-project#which-features-make-up-the-react-teams-full-stack-architecture-vision).
|
||||
Today we're releasing React Server Components as semver stable in React 19. This means libraries that ship Server Components and Server Actions can target React 19 as a peer dependency for use in frameworks that support the [Full-stack React Architecture](/learn/start-a-new-react-project#which-features-make-up-the-react-teams-full-stack-architecture-vision).
|
||||
|
||||
<DeepDive>
|
||||
|
||||
@@ -277,303 +277,7 @@ TODO:
|
||||
|
||||
</DeepDive>
|
||||
|
||||
### Server Components without a Server {/*server-components-without-a-server*/}
|
||||
Server components can run at build time to read from the filesystem or fetch static content, so a web server is not required. For example, you may want to read static data from a content management system.
|
||||
|
||||
Without Server Components, it's common to fetch static data on the client with an Effect:
|
||||
```js
|
||||
// bundle.js
|
||||
import marked from 'marked'; // 35.9K (11.2K gzipped)
|
||||
import sanitizeHtml from 'sanitize-html'; // 206K (63.3K gzipped)
|
||||
|
||||
function Page({page}) {
|
||||
const [content, setContent] = useState('');
|
||||
// NOTE: loads *after* first page render.
|
||||
useEffect(() => {
|
||||
fetch(`/api/content/${page}`).then((data) => {
|
||||
setContent(data.content);
|
||||
});
|
||||
}, [page]);
|
||||
|
||||
return <div>{sanitizeHtml(marked(content))}</div>;
|
||||
}
|
||||
```
|
||||
```js
|
||||
// api.js
|
||||
app.get(`/api/content/:page`, async (req, res) => {
|
||||
const page = req.params.page;
|
||||
const content = await file.readFile(`${page}.md`);
|
||||
res.send({content});
|
||||
});
|
||||
```
|
||||
|
||||
This pattern means users need to download and parse an additional 75K (gzipped) of libraries, and wait for a second request to fetch the data after the page loads, just to render static content that will not change for the lifetime of the page.
|
||||
|
||||
With Server Components, you can render these components once at build time:
|
||||
|
||||
```js
|
||||
import marked from 'marked'; // Not included in bundle
|
||||
import sanitizeHtml from 'sanitize-html'; // Not included in bundle
|
||||
|
||||
async function Page({page}) {
|
||||
// NOTE: loads *during* render, when the app is built.
|
||||
const content = await file.readFile(`${page}.md`);
|
||||
|
||||
return <div>{sanitizeHtml(marked(content))}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
The rendered output can then be server-side rendered (SSR) to HTML and uploaded to a CDN. When the app loads, the client will not see the original `Page` component, or the expensive libraries for rendering the markdown. The client will only see the rendered output:
|
||||
|
||||
```js
|
||||
<div><!-- html for markdown --></div>
|
||||
```
|
||||
|
||||
This means the content is visible during first page load, and the bundle does not include the expensive libraries needed to render the static content.
|
||||
|
||||
<Note>
|
||||
|
||||
You may notice that the Server Component above is an async function:
|
||||
|
||||
```js
|
||||
async function Page({page}) {
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
Async Components are a new feature of Server Components that allow you to `await` in render.
|
||||
|
||||
See [Async components with Server Components](#async-components-with-server-components) below.
|
||||
|
||||
</Note>
|
||||
|
||||
### Server Components with a Server {/*server-components-with-a-server*/}
|
||||
Server Components can also run on a web server during a request for a page, letting you access your data layer without having to build an API. They are rendered before your application is bundled, and can pass data and JSX as props to Client Components.
|
||||
|
||||
Without Server Components, it's common to fetch dynamic data on the client in an Effect:
|
||||
|
||||
```js
|
||||
// bundle.js
|
||||
function Note({id}) {
|
||||
const [note, setNote] = useState('');
|
||||
// NOTE: loads *after* first render.
|
||||
useEffect(() => {
|
||||
fetch(`/api/notes/${id}`).then(data => {
|
||||
setNote(data.note);
|
||||
});
|
||||
}, [id]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Author id={note.authorId} />
|
||||
<p>{note}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Author({id}) {
|
||||
const [author, setAuthor] = useState('');
|
||||
// NOTE: loads *after* Note renders.
|
||||
// Causing an expensive client-server waterfall.
|
||||
useEffect(() => {
|
||||
fetch(`/api/authors/${id}`).then(data => {
|
||||
setAuthor(data.author);
|
||||
});
|
||||
}, [id]);
|
||||
|
||||
return <span>By: {author.name}</span>;
|
||||
}
|
||||
```
|
||||
```js
|
||||
// api
|
||||
import db from './database';
|
||||
|
||||
app.get(`/api/notes/:id`, async (req, res) => {
|
||||
const note = await db.notes.get(id);
|
||||
res.send({note});
|
||||
});
|
||||
|
||||
app.get(`/api/authors/:id`, async (req, res) => {
|
||||
const author = await db.authors.get(id);
|
||||
res.send({author});
|
||||
});
|
||||
```
|
||||
|
||||
With Server Components, you can read the data and render it in the component:
|
||||
|
||||
```js
|
||||
import db from './database';
|
||||
|
||||
async function Note({id}) {
|
||||
// NOTE: loads *during* render.
|
||||
const note = await db.notes.get(id);
|
||||
return (
|
||||
<div>
|
||||
<Author id={note.authorId} />
|
||||
<p>{note}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
async function Author({id}) {
|
||||
// NOTE: loads *after* Node,
|
||||
// but is fast if data is co-located.
|
||||
const author = await db.authors.get(id);
|
||||
return <span>By: {author.name}</span>;
|
||||
}
|
||||
```
|
||||
|
||||
The bundler then combines the data, rendered Server Components and dynamic Client Components into a bundle. Optionally, that bundle can then be server-side rendered (SSR) to create the initial HTML for the page. When the page loads, the browser does not see the original `Note` and `Author` components; only the rendered output is sent to the client:
|
||||
|
||||
```js
|
||||
<div>
|
||||
<span>By: The React Team</span>
|
||||
<p>React 19 Beta is...</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
Server Components can be made dynamic by re-fetching them from a server, where they can access the data and render again. This new application architecture combines the simple “request/response” mental model of server-centric Multi-Page Apps with the seamless interactivity of client-centric Single-Page Apps, giving you the best of both worlds.
|
||||
|
||||
<DeepDive>
|
||||
|
||||
#### How do I make Server Components dynamic? {/*how-do-i-make-server-components-dynamic*/}
|
||||
|
||||
TODO: use a router, re-fetch them.
|
||||
|
||||
</DeepDive>
|
||||
|
||||
### Directives `"use client"` and `"use server"` {/*directives*/}
|
||||
|
||||
[Directives](/reference/react/directives) are bundler features designed for full-stack React frameworks. They mark the “split points” between the two environments:
|
||||
|
||||
- `"use client"` instructs the bundler to generate a `<script>` tag (like Astro Islands).
|
||||
- `"use server"` tells the bundler to generate a POST endpoint (like tRPC Mutations).
|
||||
|
||||
Together, directives let you write reusable components that compose client-side interactivity with the related server-side logic. `"use client"` composes client-side interactivity on the server with Server Components, and `"use server"` composes server-side code on the client with Server Actions.
|
||||
|
||||
<DeepDive>
|
||||
|
||||
#### How do bundler authors support Directives? {/*how-do-bundler-authors-support-directives*/}
|
||||
|
||||
TODO
|
||||
|
||||
</DeepDive>
|
||||
|
||||
### Adding interactivity to Server Components {/*adding-interactivity-to-server-components*/}
|
||||
|
||||
Server Components are not sent to the browser, so they cannot use interactive APIs like `useState`. To add interactivity to Server Components, you can compose them with Client Component using the `"use client"` directive.
|
||||
|
||||
<Note>
|
||||
|
||||
#### There is no directive for Server Components. {/*there-is-no-directive-for-server-components*/}
|
||||
|
||||
A common misunderstanding is that Server Components are denoted by `"use server"`, but there is no directive for Server Components. The `"use server"` directive is used for Server Actions.
|
||||
|
||||
</Note>
|
||||
|
||||
|
||||
In the following example, the `Notes` Server Component imports an `Expandable` Client Component that uses state to toggle it's `expanded` state:
|
||||
```js
|
||||
// Server Component
|
||||
import Exapandable from './Expandable';
|
||||
|
||||
async function Notes() {
|
||||
const notes = await db.notes.getAll();
|
||||
return (
|
||||
<div>
|
||||
{notes.map(note => (
|
||||
<Expandable>
|
||||
<p note={note} />
|
||||
</Expandable>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
```js
|
||||
// Client Component
|
||||
"use client"
|
||||
|
||||
export default function Expandable({children}) {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
>
|
||||
Toggle
|
||||
</button>
|
||||
{expanded && children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
This works by first rendering `Notes` as a Server Component, and then instructing the bundler to create a bundle for the Client Component `Expandable`. In the browser, the Client Components will see output of the Server Components passed as props:
|
||||
|
||||
```js
|
||||
<head>
|
||||
<!-- the bundle for Client Components -->
|
||||
<script src="bundle.js" />
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<Expandable>
|
||||
<p>this is the first note</p>
|
||||
</Expandable>
|
||||
<Expandable>
|
||||
<p>this is the second note</p>
|
||||
</Expandable>
|
||||
<!--...-->
|
||||
</div>
|
||||
</body>
|
||||
```
|
||||
|
||||
### Async components with Server Components {/*async-components-with-server-components*/}
|
||||
|
||||
Server Components introduce a new way to write Components using async/await. When you `await` in an async component, React will suspend and wait for the promise to resolve before resuming rendering. This works across server/client boundaries with streaming support for Suspense.
|
||||
|
||||
You can even create a promise on the server, and await it on the client:
|
||||
|
||||
```js
|
||||
// Server Component
|
||||
import db from './database';
|
||||
|
||||
async function Page({id}) {
|
||||
// Will suspend the Server Component.
|
||||
const note = await db.notes.get(id);
|
||||
|
||||
// NOTE: not awaited, will start here and await on the client.
|
||||
const commentsPromise = db.comments.get(note.id);
|
||||
return (
|
||||
<div>
|
||||
{note}
|
||||
<Suspense fallback={<p>Loading Comments...</p>}>
|
||||
<Comments commentsPromise={commentsPromise} />
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
// Client Component
|
||||
"use client";
|
||||
import {use} from 'react';
|
||||
|
||||
function Comments({commentsPromise}) {
|
||||
// NOTE: this will resume the promise from the server.
|
||||
// It will suspend until the data is available.
|
||||
const comments = use(commentsPromise);
|
||||
return comments.map(commment => <p>{comment}</p>);
|
||||
}
|
||||
```
|
||||
|
||||
The `note` content is important data for the page to render, so we `await` it on the server. The comments are below the fold and lower-priority, so we start the promise on the server, and wait for it on the client with the `use` API. This will Suspend on the client, without blocking the `note` content from rendering.
|
||||
|
||||
Since async components are [not supported on the client](#why-cant-i-use-async-components-on-the-client), we await the promise with `use`.
|
||||
|
||||
## React Server Actions (RSA) {/*react-server-actions-rsa*/}
|
||||
### React Server Actions (RSA) {/*react-server-actions-rsa*/}
|
||||
|
||||
Server Actions allow Client Components to call async functions executed on the server.
|
||||
|
||||
@@ -589,194 +293,6 @@ TODO
|
||||
|
||||
</DeepDive>
|
||||
|
||||
### Creating a Server Action from a Server Component {/*creating-a-server-action-from-a-server-component*/}
|
||||
|
||||
Server Components can define Server Actions with the `"use server"` directive:
|
||||
|
||||
```js [[2, 7, "'use server'"], [1, 5, "createNoteAction"], [1, 12, "createNoteAction"]]
|
||||
// Server Component
|
||||
import Button from './Button';
|
||||
|
||||
function EmptyNote () {
|
||||
async function createNoteAction() {
|
||||
// Server Action
|
||||
'use server';
|
||||
|
||||
await db.notes.create();
|
||||
}
|
||||
|
||||
return <Button onClick={createNoteAction}/>;
|
||||
}
|
||||
```
|
||||
|
||||
When React renders the `EmptyNote` Server Component, it will create a reference to the `createNoteAction` function, and pass that reference to the `Button` Client Component. When the button is clicked, React will send a request to the server to execute the `createNoteAction` function with the reference provided:
|
||||
|
||||
```js {5}
|
||||
"use client";
|
||||
|
||||
export default function Button({onClick}) {
|
||||
console.log(onClick);
|
||||
// {$$typeof: Symbol.for("react.server.reference"), $$id: 'createNoteAction'}
|
||||
return <button onClick={onClick}>Create Empty Note</button>
|
||||
}
|
||||
```
|
||||
|
||||
For more, see the docs for [`"use server"`](/reference/react/use-server).
|
||||
|
||||
|
||||
### Importing Server Actions from Client Components {/*importing-server-actions-from-client-components*/}
|
||||
|
||||
Client Components can import Server Actions from files that use the `"use server"` directive:
|
||||
|
||||
```js [[1, 3, "createNoteAction"]]
|
||||
"use server";
|
||||
|
||||
export async function createNoteAction() {
|
||||
await db.notes.create();
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
When the bundler builds the `EmptyNote` Client Component, it will create a reference to the `createNoteAction` function in the bundle. When the `button` is clicked, React will send a request to the server to execute the `createNoteAction` function using the reference provided:
|
||||
|
||||
```js [[1, 2, "createNoteAction"], [1, 5, "createNoteAction"], [1, 7, "createNoteAction"]]
|
||||
"use client";
|
||||
import {createNoteAction} from './actions';
|
||||
|
||||
function EmptyNote() {
|
||||
console.log(createNoteAction);
|
||||
// {$$typeof: Symbol.for("react.server.reference"), $$id: 'createNoteAction'}
|
||||
<button onClick={createNoteAction} />
|
||||
}
|
||||
```
|
||||
|
||||
For more, see the docs for [`"use server"`](/reference/react/use-server).
|
||||
|
||||
### Composing Server Actions with Actions {/*composing-server-actions-with-actions*/}
|
||||
|
||||
Server Actions can be composed with Actions on the client:
|
||||
|
||||
```js [[1, 3, "updateName"]]
|
||||
"use server";
|
||||
|
||||
export async function updateName(name) {
|
||||
if (!name) {
|
||||
return {error: 'Name is required'};
|
||||
}
|
||||
await db.users.updateName(name);
|
||||
}
|
||||
```
|
||||
|
||||
```js [[1, 3, "updateName"], [1, 13, "updateName"], [2, 11, "submitAction"], [2, 23, "submitAction"]]
|
||||
"use client";
|
||||
|
||||
import {updateName} from './actions';
|
||||
|
||||
function UpdateName() {
|
||||
const [name, setName] = useState('');
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
const [isPending, startTransition] = useTransition();
|
||||
|
||||
const submitAction = async () => {
|
||||
startTransition(async () => {
|
||||
const {error} = await updateName(name);
|
||||
if (!error) {
|
||||
setError(error);
|
||||
} else {
|
||||
setName('');
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<form action={submitAction}>
|
||||
<input type="text" name="name" disabled={isPending}/>
|
||||
{state.error && <span>Failed: {state.error}</span>}
|
||||
</form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
This allows you to access the `isPending` state of the Server Action by wrapping it in an Action on the client.
|
||||
|
||||
For more, see the docs for [Calling a Server Action outside of `<form>`](/reference/react/use-server#calling-a-server-action-outside-of-form)
|
||||
|
||||
### Form Actions with Server Actions {/*form-actions-with-server-actions*/}
|
||||
|
||||
Server Actions work with the new Form features in React 19.
|
||||
|
||||
You can pass a Server Action to a Form to automatically submit the form to the server:
|
||||
|
||||
|
||||
```js [[1, 3, "updateName"], [1, 7, "updateName"]]
|
||||
"use client";
|
||||
|
||||
import {updateName} from './actions';
|
||||
|
||||
function UpdateName() {
|
||||
return (
|
||||
<form action={updateName}>
|
||||
<input type="text" name="name" />
|
||||
</form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
When the Form submission succeeds, React will automatically reset the form. You can add `useActionState` to access the pending state, last response, or to support progressive enhancement.
|
||||
|
||||
For more, see the docs for [Server Actions in Forms](/reference/react/use-server#server-actions-in-forms).
|
||||
|
||||
### Server Actions with `useActionState` {/*server-actions-with-use-action-state*/}
|
||||
|
||||
You can compose Server Actions with `useActionState` for the common case where you just need access to the action pending state and last returned response:
|
||||
|
||||
```js [[1, 3, "updateName"], [1, 6, "updateName"], [2, 6, "submitAction"], [2, 9, "submitAction"]]
|
||||
"use client";
|
||||
|
||||
import {updateName} from './actions';
|
||||
|
||||
function UpdateName() {
|
||||
const [submitAction, state, isPending] = useActionState(updateName);
|
||||
|
||||
return (
|
||||
<form action={submitAction}>
|
||||
<input type="text" name="name" disabled={isPending}/>
|
||||
{state.error && <span>Failed: {state.error}</span>}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
When using `useActionState` with Server Actions, React will also automatically replay form submissions entered before hydration finishes. This means users can interact with your app even before the app has hydrated.
|
||||
|
||||
For more, see the docs for [`useActionState`](/reference/react-dom/hooks/useFormState).
|
||||
|
||||
### Progressive enhancement with `useActionState` {/*progressive-enhancement-with-useactionstate*/}
|
||||
|
||||
Server Actions also support progressive enhancement with the second argument of `useActionState`.
|
||||
|
||||
```js [[1, 3, "updateName"], [1, 6, "updateName"], [2, 6, "/name/update"], [3, 6, "submitAction"], [3, 9, "submitAction"]]
|
||||
"use client";
|
||||
|
||||
import {updateName} from './actions';
|
||||
|
||||
function UpdateName() {
|
||||
const [submitAction] = useActionState(updateName, `/name/update`);
|
||||
|
||||
return (
|
||||
<form action={submitAction}>
|
||||
...
|
||||
</form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
When the <CodeStep step={2}>permalink</CodeStep> is provided to `useActionState`, React will redirect to the provided URL if the form is submitted before the JavaScript bundle loads.
|
||||
|
||||
For more, see the docs for [`useActionState`](/reference/react-dom/hooks/useFormState).
|
||||
|
||||
|
||||
## Improvements in React 19 {/*improvements-in-react-19*/}
|
||||
|
||||
### `ref` as a prop {/*ref-as-a-prop*/}
|
||||
|
||||
@@ -20,4 +20,12 @@ Directives provide instructions to [bundlers compatible with React Server Compon
|
||||
## Source code directives {/*source-code-directives*/}
|
||||
|
||||
* [`'use client'`](/reference/react/use-client) lets you mark what code runs on the client.
|
||||
* [`'use server'`](/reference/react/use-server) marks server-side functions that can be called from client-side code.
|
||||
* [`'use server'`](/reference/react/use-server) marks server-side functions that can be called from client-side code.
|
||||
|
||||
<DeepDive>
|
||||
|
||||
#### How do bundler authors support Directives? {/*how-do-bundler-authors-support-directives*/}
|
||||
|
||||
TODO
|
||||
|
||||
</DeepDive>
|
||||
23
src/content/reference/full-stack/index.md
Normal file
23
src/content/reference/full-stack/index.md
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
title: React Full-stack Architecture
|
||||
---
|
||||
|
||||
<Intro>
|
||||
|
||||
This section provides detailed documentation for the Full-stack React Architecture. For an introduction to React, please visit the [Learn](/learn) section.
|
||||
|
||||
</Intro>
|
||||
|
||||
---
|
||||
|
||||
## React Server Components {/*react-server-components*/}
|
||||
|
||||
TODO
|
||||
|
||||
## React Server Actions {/*react-server-actions*/}
|
||||
|
||||
TODO
|
||||
|
||||
## Directives {/*directives*/}
|
||||
|
||||
TODO
|
||||
214
src/content/reference/full-stack/server-actions.md
Normal file
214
src/content/reference/full-stack/server-actions.md
Normal file
@@ -0,0 +1,214 @@
|
||||
---
|
||||
title: React Server Actions (RSA)
|
||||
canary: true
|
||||
---
|
||||
|
||||
<Intro>
|
||||
|
||||
Server Actions allow Client Components to call async functions executed on the server.
|
||||
|
||||
</Intro>
|
||||
|
||||
<InlineToc />
|
||||
|
||||
<DeepDive>
|
||||
|
||||
#### How do I use Server Actions? {/*how-do-i-use-server-actions*/}
|
||||
|
||||
TODO
|
||||
|
||||
</DeepDive>
|
||||
|
||||
|
||||
When a Server Action is defined with the `"use server"` directive, your framework will automatically create a reference to the server function, and pass that reference to the Client Component. When that function is called on the client, React will send a request to the server to execute the function, and return the result.
|
||||
|
||||
Server Actions can be created in Server Components and passed as props to Client Components, or they can be imported and used in Client Components.
|
||||
|
||||
|
||||
|
||||
### Creating a Server Action from a Server Component {/*creating-a-server-action-from-a-server-component*/}
|
||||
|
||||
Server Components can define Server Actions with the `"use server"` directive:
|
||||
|
||||
```js [[2, 7, "'use server'"], [1, 5, "createNoteAction"], [1, 12, "createNoteAction"]]
|
||||
// Server Component
|
||||
import Button from './Button';
|
||||
|
||||
function EmptyNote () {
|
||||
async function createNoteAction() {
|
||||
// Server Action
|
||||
'use server';
|
||||
|
||||
await db.notes.create();
|
||||
}
|
||||
|
||||
return <Button onClick={createNoteAction}/>;
|
||||
}
|
||||
```
|
||||
|
||||
When React renders the `EmptyNote` Server Component, it will create a reference to the `createNoteAction` function, and pass that reference to the `Button` Client Component. When the button is clicked, React will send a request to the server to execute the `createNoteAction` function with the reference provided:
|
||||
|
||||
```js {5}
|
||||
"use client";
|
||||
|
||||
export default function Button({onClick}) {
|
||||
console.log(onClick);
|
||||
// {$$typeof: Symbol.for("react.server.reference"), $$id: 'createNoteAction'}
|
||||
return <button onClick={onClick}>Create Empty Note</button>
|
||||
}
|
||||
```
|
||||
|
||||
For more, see the docs for [`"use server"`](/reference/react/use-server).
|
||||
|
||||
|
||||
### Importing Server Actions from Client Components {/*importing-server-actions-from-client-components*/}
|
||||
|
||||
Client Components can import Server Actions from files that use the `"use server"` directive:
|
||||
|
||||
```js [[1, 3, "createNoteAction"]]
|
||||
"use server";
|
||||
|
||||
export async function createNoteAction() {
|
||||
await db.notes.create();
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
When the bundler builds the `EmptyNote` Client Component, it will create a reference to the `createNoteAction` function in the bundle. When the `button` is clicked, React will send a request to the server to execute the `createNoteAction` function using the reference provided:
|
||||
|
||||
```js [[1, 2, "createNoteAction"], [1, 5, "createNoteAction"], [1, 7, "createNoteAction"]]
|
||||
"use client";
|
||||
import {createNoteAction} from './actions';
|
||||
|
||||
function EmptyNote() {
|
||||
console.log(createNoteAction);
|
||||
// {$$typeof: Symbol.for("react.server.reference"), $$id: 'createNoteAction'}
|
||||
<button onClick={createNoteAction} />
|
||||
}
|
||||
```
|
||||
|
||||
For more, see the docs for [`"use server"`](/reference/react/use-server).
|
||||
|
||||
### Composing Server Actions with Actions {/*composing-server-actions-with-actions*/}
|
||||
|
||||
Server Actions can be composed with Actions on the client:
|
||||
|
||||
```js [[1, 3, "updateName"]]
|
||||
"use server";
|
||||
|
||||
export async function updateName(name) {
|
||||
if (!name) {
|
||||
return {error: 'Name is required'};
|
||||
}
|
||||
await db.users.updateName(name);
|
||||
}
|
||||
```
|
||||
|
||||
```js [[1, 3, "updateName"], [1, 13, "updateName"], [2, 11, "submitAction"], [2, 23, "submitAction"]]
|
||||
"use client";
|
||||
|
||||
import {updateName} from './actions';
|
||||
|
||||
function UpdateName() {
|
||||
const [name, setName] = useState('');
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
const [isPending, startTransition] = useTransition();
|
||||
|
||||
const submitAction = async () => {
|
||||
startTransition(async () => {
|
||||
const {error} = await updateName(name);
|
||||
if (!error) {
|
||||
setError(error);
|
||||
} else {
|
||||
setName('');
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<form action={submitAction}>
|
||||
<input type="text" name="name" disabled={isPending}/>
|
||||
{state.error && <span>Failed: {state.error}</span>}
|
||||
</form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
This allows you to access the `isPending` state of the Server Action by wrapping it in an Action on the client.
|
||||
|
||||
For more, see the docs for [Calling a Server Action outside of `<form>`](/reference/react/use-server#calling-a-server-action-outside-of-form)
|
||||
|
||||
### Form Actions with Server Actions {/*form-actions-with-server-actions*/}
|
||||
|
||||
Server Actions work with the new Form features in React 19.
|
||||
|
||||
You can pass a Server Action to a Form to automatically submit the form to the server:
|
||||
|
||||
|
||||
```js [[1, 3, "updateName"], [1, 7, "updateName"]]
|
||||
"use client";
|
||||
|
||||
import {updateName} from './actions';
|
||||
|
||||
function UpdateName() {
|
||||
return (
|
||||
<form action={updateName}>
|
||||
<input type="text" name="name" />
|
||||
</form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
When the Form submission succeeds, React will automatically reset the form. You can add `useActionState` to access the pending state, last response, or to support progressive enhancement.
|
||||
|
||||
For more, see the docs for [Server Actions in Forms](/reference/react/use-server#server-actions-in-forms).
|
||||
|
||||
### Server Actions with `useActionState` {/*server-actions-with-use-action-state*/}
|
||||
|
||||
You can compose Server Actions with `useActionState` for the common case where you just need access to the action pending state and last returned response:
|
||||
|
||||
```js [[1, 3, "updateName"], [1, 6, "updateName"], [2, 6, "submitAction"], [2, 9, "submitAction"]]
|
||||
"use client";
|
||||
|
||||
import {updateName} from './actions';
|
||||
|
||||
function UpdateName() {
|
||||
const [submitAction, state, isPending] = useActionState(updateName);
|
||||
|
||||
return (
|
||||
<form action={submitAction}>
|
||||
<input type="text" name="name" disabled={isPending}/>
|
||||
{state.error && <span>Failed: {state.error}</span>}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
When using `useActionState` with Server Actions, React will also automatically replay form submissions entered before hydration finishes. This means users can interact with your app even before the app has hydrated.
|
||||
|
||||
For more, see the docs for [`useActionState`](/reference/react-dom/hooks/useFormState).
|
||||
|
||||
### Progressive enhancement with `useActionState` {/*progressive-enhancement-with-useactionstate*/}
|
||||
|
||||
Server Actions also support progressive enhancement with the second argument of `useActionState`.
|
||||
|
||||
```js [[1, 3, "updateName"], [1, 6, "updateName"], [2, 6, "/name/update"], [3, 6, "submitAction"], [3, 9, "submitAction"]]
|
||||
"use client";
|
||||
|
||||
import {updateName} from './actions';
|
||||
|
||||
function UpdateName() {
|
||||
const [submitAction] = useActionState(updateName, `/name/update`);
|
||||
|
||||
return (
|
||||
<form action={submitAction}>
|
||||
...
|
||||
</form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
When the <CodeStep step={2}>permalink</CodeStep> is provided to `useActionState`, React will redirect to the provided URL if the form is submitted before the JavaScript bundle loads.
|
||||
|
||||
For more, see the docs for [`useActionState`](/reference/react-dom/hooks/useFormState).
|
||||
318
src/content/reference/full-stack/server-components.md
Normal file
318
src/content/reference/full-stack/server-components.md
Normal file
@@ -0,0 +1,318 @@
|
||||
---
|
||||
title: React Server Components (RSC)
|
||||
canary: true
|
||||
---
|
||||
|
||||
<Intro>
|
||||
|
||||
Server Components are a new type of Component that renders ahead of time, before bundling, in an environment separate from your client app or SSR server.
|
||||
|
||||
</Intro>
|
||||
|
||||
<InlineToc />
|
||||
|
||||
<DeepDive>
|
||||
|
||||
#### How do I use Server Components? {/*how-do-i-use-server-components*/}
|
||||
|
||||
We first announced React Server Components (RSC) in a [demo in December 2020](https://legacy.reactjs.org/blog/2020/12/21/data-fetching-with-react-server-components.html). In 2022, we merged the [RFC for React Server Components](https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md) and the [RFC for React Server Module Conventions](https://github.com/reactjs/rfcs/blob/main/text/0227-server-module-conventions.md) and partnered with Next.js for the first implementation in the Next.js 13 App Router beta. We worked with the Next.js team to implement Server Components via the stable Canary channel, and Server Components shipped as the default in Next.js 14.
|
||||
|
||||
We will continue working with bundlers and framework authors to expand support for React Server Components.
|
||||
|
||||
TODO:
|
||||
- need a framework
|
||||
- bundler: link to "How do bundler authors support Directives?"
|
||||
- router: link to "How do I make Server Components dynamic?"
|
||||
|
||||
|
||||
</DeepDive>
|
||||
|
||||
|
||||
### Server Components without a Server {/*server-components-without-a-server*/}
|
||||
Server components can run at build time to read from the filesystem or fetch static content, so a web server is not required. For example, you may want to read static data from a content management system.
|
||||
|
||||
Without Server Components, it's common to fetch static data on the client with an Effect:
|
||||
```js
|
||||
// bundle.js
|
||||
import marked from 'marked'; // 35.9K (11.2K gzipped)
|
||||
import sanitizeHtml from 'sanitize-html'; // 206K (63.3K gzipped)
|
||||
|
||||
function Page({page}) {
|
||||
const [content, setContent] = useState('');
|
||||
// NOTE: loads *after* first page render.
|
||||
useEffect(() => {
|
||||
fetch(`/api/content/${page}`).then((data) => {
|
||||
setContent(data.content);
|
||||
});
|
||||
}, [page]);
|
||||
|
||||
return <div>{sanitizeHtml(marked(content))}</div>;
|
||||
}
|
||||
```
|
||||
```js
|
||||
// api.js
|
||||
app.get(`/api/content/:page`, async (req, res) => {
|
||||
const page = req.params.page;
|
||||
const content = await file.readFile(`${page}.md`);
|
||||
res.send({content});
|
||||
});
|
||||
```
|
||||
|
||||
This pattern means users need to download and parse an additional 75K (gzipped) of libraries, and wait for a second request to fetch the data after the page loads, just to render static content that will not change for the lifetime of the page.
|
||||
|
||||
With Server Components, you can render these components once at build time:
|
||||
|
||||
```js
|
||||
import marked from 'marked'; // Not included in bundle
|
||||
import sanitizeHtml from 'sanitize-html'; // Not included in bundle
|
||||
|
||||
async function Page({page}) {
|
||||
// NOTE: loads *during* render, when the app is built.
|
||||
const content = await file.readFile(`${page}.md`);
|
||||
|
||||
return <div>{sanitizeHtml(marked(content))}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
The rendered output can then be server-side rendered (SSR) to HTML and uploaded to a CDN. When the app loads, the client will not see the original `Page` component, or the expensive libraries for rendering the markdown. The client will only see the rendered output:
|
||||
|
||||
```js
|
||||
<div><!-- html for markdown --></div>
|
||||
```
|
||||
|
||||
This means the content is visible during first page load, and the bundle does not include the expensive libraries needed to render the static content.
|
||||
|
||||
<Note>
|
||||
|
||||
You may notice that the Server Component above is an async function:
|
||||
|
||||
```js
|
||||
async function Page({page}) {
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
Async Components are a new feature of Server Components that allow you to `await` in render.
|
||||
|
||||
See [Async components with Server Components](#async-components-with-server-components) below.
|
||||
|
||||
</Note>
|
||||
|
||||
### Server Components with a Server {/*server-components-with-a-server*/}
|
||||
Server Components can also run on a web server during a request for a page, letting you access your data layer without having to build an API. They are rendered before your application is bundled, and can pass data and JSX as props to Client Components.
|
||||
|
||||
Without Server Components, it's common to fetch dynamic data on the client in an Effect:
|
||||
|
||||
```js
|
||||
// bundle.js
|
||||
function Note({id}) {
|
||||
const [note, setNote] = useState('');
|
||||
// NOTE: loads *after* first render.
|
||||
useEffect(() => {
|
||||
fetch(`/api/notes/${id}`).then(data => {
|
||||
setNote(data.note);
|
||||
});
|
||||
}, [id]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Author id={note.authorId} />
|
||||
<p>{note}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Author({id}) {
|
||||
const [author, setAuthor] = useState('');
|
||||
// NOTE: loads *after* Note renders.
|
||||
// Causing an expensive client-server waterfall.
|
||||
useEffect(() => {
|
||||
fetch(`/api/authors/${id}`).then(data => {
|
||||
setAuthor(data.author);
|
||||
});
|
||||
}, [id]);
|
||||
|
||||
return <span>By: {author.name}</span>;
|
||||
}
|
||||
```
|
||||
```js
|
||||
// api
|
||||
import db from './database';
|
||||
|
||||
app.get(`/api/notes/:id`, async (req, res) => {
|
||||
const note = await db.notes.get(id);
|
||||
res.send({note});
|
||||
});
|
||||
|
||||
app.get(`/api/authors/:id`, async (req, res) => {
|
||||
const author = await db.authors.get(id);
|
||||
res.send({author});
|
||||
});
|
||||
```
|
||||
|
||||
With Server Components, you can read the data and render it in the component:
|
||||
|
||||
```js
|
||||
import db from './database';
|
||||
|
||||
async function Note({id}) {
|
||||
// NOTE: loads *during* render.
|
||||
const note = await db.notes.get(id);
|
||||
return (
|
||||
<div>
|
||||
<Author id={note.authorId} />
|
||||
<p>{note}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
async function Author({id}) {
|
||||
// NOTE: loads *after* Node,
|
||||
// but is fast if data is co-located.
|
||||
const author = await db.authors.get(id);
|
||||
return <span>By: {author.name}</span>;
|
||||
}
|
||||
```
|
||||
|
||||
The bundler then combines the data, rendered Server Components and dynamic Client Components into a bundle. Optionally, that bundle can then be server-side rendered (SSR) to create the initial HTML for the page. When the page loads, the browser does not see the original `Note` and `Author` components; only the rendered output is sent to the client:
|
||||
|
||||
```js
|
||||
<div>
|
||||
<span>By: The React Team</span>
|
||||
<p>React 19 Beta is...</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
Server Components can be made dynamic by re-fetching them from a server, where they can access the data and render again. This new application architecture combines the simple “request/response” mental model of server-centric Multi-Page Apps with the seamless interactivity of client-centric Single-Page Apps, giving you the best of both worlds.
|
||||
|
||||
<DeepDive>
|
||||
|
||||
#### How do I make Server Components dynamic? {/*how-do-i-make-server-components-dynamic*/}
|
||||
|
||||
TODO: use a router, re-fetch them.
|
||||
|
||||
</DeepDive>
|
||||
|
||||
### Directives `"use client"` and `"use server"` {/*directives*/}
|
||||
|
||||
[Directives](/reference/react/directives) are bundler features designed for full-stack React frameworks. They mark the “split points” between the two environments:
|
||||
|
||||
- `"use client"` instructs the bundler to generate a `<script>` tag (like Astro Islands).
|
||||
- `"use server"` tells the bundler to generate a POST endpoint (like tRPC Mutations).
|
||||
|
||||
Together, directives let you write reusable components that compose client-side interactivity with the related server-side logic. `"use client"` composes client-side interactivity on the server with Server Components, and `"use server"` composes server-side code on the client with Server Actions.
|
||||
|
||||
|
||||
### Adding interactivity to Server Components {/*adding-interactivity-to-server-components*/}
|
||||
|
||||
Server Components are not sent to the browser, so they cannot use interactive APIs like `useState`. To add interactivity to Server Components, you can compose them with Client Component using the `"use client"` directive.
|
||||
|
||||
<Note>
|
||||
|
||||
#### There is no directive for Server Components. {/*there-is-no-directive-for-server-components*/}
|
||||
|
||||
A common misunderstanding is that Server Components are denoted by `"use server"`, but there is no directive for Server Components. The `"use server"` directive is used for Server Actions.
|
||||
|
||||
</Note>
|
||||
|
||||
|
||||
In the following example, the `Notes` Server Component imports an `Expandable` Client Component that uses state to toggle its `expanded` state:
|
||||
```js
|
||||
// Server Component
|
||||
import Exapandable from './Expandable';
|
||||
|
||||
async function Notes() {
|
||||
const notes = await db.notes.getAll();
|
||||
return (
|
||||
<div>
|
||||
{notes.map(note => (
|
||||
<Expandable key={note.id}>
|
||||
<p note={note} />
|
||||
</Expandable>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
```js
|
||||
// Client Component
|
||||
"use client"
|
||||
|
||||
export default function Expandable({children}) {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
>
|
||||
Toggle
|
||||
</button>
|
||||
{expanded && children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
This works by first rendering `Notes` as a Server Component, and then instructing the bundler to create a bundle for the Client Component `Expandable`. In the browser, the Client Components will see output of the Server Components passed as props:
|
||||
|
||||
```js
|
||||
<head>
|
||||
<!-- the bundle for Client Components -->
|
||||
<script src="bundle.js" />
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<Expandable key={1}>
|
||||
<p>this is the first note</p>
|
||||
</Expandable>
|
||||
<Expandable key={2}>
|
||||
<p>this is the second note</p>
|
||||
</Expandable>
|
||||
<!--...-->
|
||||
</div>
|
||||
</body>
|
||||
```
|
||||
|
||||
### Async components with Server Components {/*async-components-with-server-components*/}
|
||||
|
||||
Server Components introduce a new way to write Components using async/await. When you `await` in an async component, React will suspend and wait for the promise to resolve before resuming rendering. This works across server/client boundaries with streaming support for Suspense.
|
||||
|
||||
You can even create a promise on the server, and await it on the client:
|
||||
|
||||
```js
|
||||
// Server Component
|
||||
import db from './database';
|
||||
|
||||
async function Page({id}) {
|
||||
// Will suspend the Server Component.
|
||||
const note = await db.notes.get(id);
|
||||
|
||||
// NOTE: not awaited, will start here and await on the client.
|
||||
const commentsPromise = db.comments.get(note.id);
|
||||
return (
|
||||
<div>
|
||||
{note}
|
||||
<Suspense fallback={<p>Loading Comments...</p>}>
|
||||
<Comments commentsPromise={commentsPromise} />
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
// Client Component
|
||||
"use client";
|
||||
import {use} from 'react';
|
||||
|
||||
function Comments({commentsPromise}) {
|
||||
// NOTE: this will resume the promise from the server.
|
||||
// It will suspend until the data is available.
|
||||
const comments = use(commentsPromise);
|
||||
return comments.map(commment => <p>{comment}</p>);
|
||||
}
|
||||
```
|
||||
|
||||
The `note` content is important data for the page to render, so we `await` it on the server. The comments are below the fold and lower-priority, so we start the promise on the server, and wait for it on the client with the `use` API. This will Suspend on the client, without blocking the `note` content from rendering.
|
||||
|
||||
Since async components are [not supported on the client](#why-cant-i-use-async-components-on-the-client), we await the promise with `use`.
|
||||
@@ -149,23 +149,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Directives",
|
||||
"path": "/reference/react/directives",
|
||||
"canary": true,
|
||||
"routes": [
|
||||
{
|
||||
"title": "'use client'",
|
||||
"path": "/reference/react/use-client",
|
||||
"canary": true
|
||||
},
|
||||
{
|
||||
"title": "'use server'",
|
||||
"path": "/reference/react/use-server",
|
||||
"canary": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hasSectionHeader": true,
|
||||
"sectionHeader": "react-dom@18.2.0"
|
||||
@@ -356,19 +339,57 @@
|
||||
},
|
||||
{
|
||||
"title": "Overview",
|
||||
"path": "/reference/rules"
|
||||
"path": "/reference/rules",
|
||||
"routes": [
|
||||
{
|
||||
"title": "Components and Hooks must be pure",
|
||||
"path": "/reference/rules/components-and-hooks-must-be-pure"
|
||||
},
|
||||
{
|
||||
"title": "React calls Components and Hooks",
|
||||
"path": "/reference/rules/react-calls-components-and-hooks"
|
||||
},
|
||||
{
|
||||
"title": "Rules of Hooks",
|
||||
"path": "/reference/rules/rules-of-hooks"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Components and Hooks must be pure",
|
||||
"path": "/reference/rules/components-and-hooks-must-be-pure"
|
||||
"hasSectionHeader": true,
|
||||
"sectionHeader": "React Full-stack Architecture"
|
||||
},
|
||||
{
|
||||
"title": "React calls Components and Hooks",
|
||||
"path": "/reference/rules/react-calls-components-and-hooks"
|
||||
"title": "Overview",
|
||||
"canary": true,
|
||||
"path": "/reference/full-stack",
|
||||
"routes": [
|
||||
{
|
||||
"title": "React Server Components",
|
||||
"path": "/reference/full-stack/server-components"
|
||||
},
|
||||
{
|
||||
"title": "React Server Actions",
|
||||
"path": "/reference/full-stack/server-actions"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Rules of Hooks",
|
||||
"path": "/reference/rules/rules-of-hooks"
|
||||
"title": "Directives",
|
||||
"path": "/reference/full-stack/directives",
|
||||
"canary": true,
|
||||
"routes": [
|
||||
{
|
||||
"title": "'use client'",
|
||||
"path": "/reference/full-stack/use-client",
|
||||
"canary": true
|
||||
},
|
||||
{
|
||||
"title": "'use server'",
|
||||
"path": "/reference/full-stack/use-server",
|
||||
"canary": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hasSectionHeader": true,
|
||||
|
||||
Reference in New Issue
Block a user