mirror of
https://github.com/vercel/next.js.git
synced 2026-09-20 02:25:18 +08:00
0156307f55
Follow-up to https://github.com/vercel/next.js/pull/95807. Makes payload size match prod sizes closer and aligns chunk count by adding a lot of client modules. I originally thought to unify them into `client.js` or such but maybe it's good to see how compilation behaves with many small files? Which is how real projects do that. ``` === dashboard.html (fixture) vs p-overview.html (real) total KB 665 637 rows (model/I/T/other) 258/111/3/1 240/125/5/27 byte share model/I/T % 43/57/0 36/62/2 median row size B 906 106 max depth 43 53 mean depth 8.9 10.5 elements per KB(model) 7.7 5.2 pure-data byte share % 18 21 client-ref elements % 35 32 objects:elements ratio 1.18 1.23 avg props per element 2.6 3.1 median children fanout 4 4 string bytes % 50 54 median string len B 12 15 row refs per KB 0.4 0.4 encoded scalars per KB 0.7 1.2 undefined markers 247 740 === docs.html (fixture) vs nextjs-docs.html (real) total KB 487 487 rows (model/I/T/other) 72/40/7/1 55/43/2/20 byte share model/I/T % 89/8/3 94/6/0 median row size B 199 340 max depth 27 32 mean depth 22.3 12.3 elements per KB(model) 0.6 0.8 pure-data byte share % 7 9 client-ref elements % 29 33 objects:elements ratio 8.67 4.92 avg props per element 2.0 2.8 median children fanout 3 4 string bytes % 47 64 median string len B 17 35 row refs per KB 0.2 0.1 encoded scalars per KB 7.4 2.9 undefined markers 3586 1377 === blog.html (fixture) vs vercel-blog.html (real) total KB 778 797 rows (model/I/T/other) 72/39/0/1 102/53/7/14 byte share model/I/T % 93/7/0 87/8/5 median row size B 145 505 max depth 34 44 mean depth 9.6 12.2 elements per KB(model) 0.7 1.1 pure-data byte share % 92 85 client-ref elements % 38 33 objects:elements ratio 31.19 16.39 avg props per element 1.9 2.7 median children fanout 3 4 string bytes % 46 48 median string len B 6 9 row refs per KB 0.1 0.1 encoded scalars per KB 0.4 0.6 undefined markers 242 426 ``` Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
619 lines
22 KiB
JavaScript
619 lines
22 KiB
JavaScript
// Models the documentation-site payload profile: a deep, element-dominated
|
|
// tree (prose + syntax-highlighted code rendered as one span per token),
|
|
// a large nav structure, and only a handful of client components.
|
|
// Prose adapted from the react.dev useState reference (CC BY 4.0,
|
|
// https://github.com/reactjs/react.dev).
|
|
import '../bench.css'
|
|
import ThemeToggle from '../ui/theme-toggle'
|
|
import DocsSearch from '../ui/docs-search'
|
|
import VersionPicker from '../ui/version-picker'
|
|
import MobileNav from '../ui/mobile-nav'
|
|
import TocScrollspy from '../ui/toc-scrollspy'
|
|
import CopyPageButton from '../ui/copy-page-button'
|
|
import DocsPager from '../ui/docs-pager'
|
|
import AnnouncementBanner from '../ui/announcement-banner'
|
|
import ScrollToTop from '../ui/scroll-to-top'
|
|
import Feedback from '../ui/feedback'
|
|
import CodeBlock, { PackageManagerBlock } from '../ui/server-code-block'
|
|
import Collapsible from '../ui/collapsible'
|
|
import DocsSidebar from '../ui/docs-sidebar'
|
|
import IconBook from '../ui/icons/book'
|
|
import IconHash from '../ui/icons/hash'
|
|
import IconMenu from '../ui/icons/menu'
|
|
import IconClose from '../ui/icons/close'
|
|
import IconFileText from '../ui/icons/file-text'
|
|
import IconList from '../ui/icons/list'
|
|
import IconMessageSquare from '../ui/icons/message-square'
|
|
import IconEdit from '../ui/icons/edit'
|
|
import IconClipboard from '../ui/icons/clipboard'
|
|
import IconChevronRight from '../ui/icons/chevron-right'
|
|
import IconArrowLeft from '../ui/icons/arrow-left'
|
|
import IconArrowRight from '../ui/icons/arrow-right'
|
|
import IconGithub from '../ui/icons/github'
|
|
import IconDiscord from '../ui/icons/discord'
|
|
import IconLightbulb from '../ui/icons/lightbulb'
|
|
import IconInfo from '../ui/icons/info'
|
|
import IconAlertTriangle from '../ui/icons/alert-triangle'
|
|
import IconLanguages from '../ui/icons/languages'
|
|
import { tokenize } from '../lib/tokenize'
|
|
import { docsTree } from '../lib/data'
|
|
|
|
export const dynamic = 'force-dynamic'
|
|
|
|
const EXAMPLES = {
|
|
basic: tokenize(`import { useState } from 'react';
|
|
|
|
function MyComponent() {
|
|
const [age, setAge] = useState(28);
|
|
const [name, setName] = useState('Taylor');
|
|
const [todos, setTodos] = useState(() => createTodos());
|
|
// ...
|
|
}`),
|
|
counter: tokenize(`import { useState } from 'react';
|
|
|
|
export default function Counter() {
|
|
const [count, setCount] = useState(0);
|
|
|
|
function handleClick() {
|
|
setCount(count + 1);
|
|
}
|
|
|
|
return (
|
|
<button onClick={handleClick}>
|
|
You pressed me {count} times
|
|
</button>
|
|
);
|
|
}`),
|
|
updater: tokenize(`function handleClick() {
|
|
setAge(a => a + 1); // setAge(42 => 43)
|
|
setAge(a => a + 1); // setAge(43 => 44)
|
|
setAge(a => a + 1); // setAge(44 => 45)
|
|
}`),
|
|
object: tokenize(`setForm({
|
|
...form,
|
|
firstName: 'Taylor'
|
|
});`),
|
|
initializer: tokenize(`function TodoList() {
|
|
const [todos, setTodos] = useState(createInitialTodos);
|
|
// ...`),
|
|
key: tokenize(`export default function App() {
|
|
const [version, setVersion] = useState(0);
|
|
|
|
function handleReset() {
|
|
setVersion(version + 1);
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<button onClick={handleReset}>Reset</button>
|
|
<Form key={version} />
|
|
</>
|
|
);
|
|
}`),
|
|
}
|
|
|
|
function H2({ id, children }) {
|
|
return (
|
|
<h2 id={id}>
|
|
<a href={'#' + id}>
|
|
{children}
|
|
<IconHash size={13} />
|
|
</a>
|
|
</h2>
|
|
)
|
|
}
|
|
function H3({ id, children }) {
|
|
return (
|
|
<h3 id={id}>
|
|
<a href={'#' + id}>
|
|
{children}
|
|
<IconHash size={12} />
|
|
</a>
|
|
</h3>
|
|
)
|
|
}
|
|
|
|
const TOC_ITEMS = [
|
|
{ id: 'reference', label: 'Reference', level: 2 },
|
|
{ id: 'usestate', label: 'useState(initialState)', level: 3 },
|
|
{ id: 'parameters', label: 'Parameters', level: 3 },
|
|
{ id: 'returns', label: 'Returns', level: 3 },
|
|
{ id: 'usage', label: 'Usage', level: 2 },
|
|
{ id: 'adding-state', label: 'Adding state', level: 3 },
|
|
{ id: 'updater-functions', label: 'Updater functions', level: 3 },
|
|
{ id: 'objects-and-arrays', label: 'Objects and arrays', level: 3 },
|
|
{ id: 'avoiding-recreating-state', label: 'Initializer functions', level: 3 },
|
|
{ id: 'resetting-state-with-a-key', label: 'Resetting with a key', level: 3 },
|
|
{ id: 'troubleshooting', label: 'Troubleshooting', level: 2 },
|
|
]
|
|
|
|
export default function DocsPage() {
|
|
return (
|
|
<>
|
|
<AnnouncementBanner>
|
|
<span>
|
|
React Conf 2026 registration is open{' '}
|
|
<a href="#">
|
|
Get tickets <IconArrowRight size={12} />
|
|
</a>
|
|
</span>
|
|
</AnnouncementBanner>
|
|
<header className="app-header">
|
|
<MobileNav
|
|
label="Toggle navigation"
|
|
openIcon={<IconMenu size={16} />}
|
|
closeIcon={<IconClose size={16} />}
|
|
/>
|
|
<nav className="crumbs" aria-label="Breadcrumb">
|
|
<IconBook size={14} />
|
|
<strong>React</strong>
|
|
<IconChevronRight size={12} />
|
|
<span>Reference</span>
|
|
<IconChevronRight size={12} />
|
|
<span className="current">useState</span>
|
|
</nav>
|
|
<div className="header-spacer" />
|
|
<DocsSearch placeholder="Search docs…" />
|
|
<VersionPicker versions={['stable', 'v15', 'v14']} current="stable" />
|
|
<ThemeToggle />
|
|
</header>
|
|
<div className="docs-shell">
|
|
<DocsSidebar tree={docsTree} version="stable" />
|
|
|
|
<article className="prose">
|
|
<div className="page-actions flex items-center gap-2">
|
|
<CopyPageButton icon={<IconClipboard size={13} />} />
|
|
<a href="#" className="text-xs text-muted">
|
|
<IconEdit size={13} /> Edit this page
|
|
</a>
|
|
<a href="#" className="text-xs text-muted">
|
|
<IconFileText size={13} /> View as Markdown
|
|
</a>
|
|
<a href="#" className="text-xs text-muted">
|
|
<IconLanguages size={13} /> Translations
|
|
</a>
|
|
</div>
|
|
<h1>useState</h1>
|
|
<p className="lead">
|
|
<code className="inline-code" dir="ltr">
|
|
useState
|
|
</code>{' '}
|
|
is a React Hook that lets you add a state variable to your
|
|
component.
|
|
</p>
|
|
<CodeBlock
|
|
lang="js"
|
|
lines={tokenize(`const [state, setState] = useState(initialState)`)}
|
|
/>
|
|
|
|
<PackageManagerBlock
|
|
variants={[
|
|
{ label: 'npm', lines: tokenize('npm install react react-dom') },
|
|
{ label: 'pnpm', lines: tokenize('pnpm add react react-dom') },
|
|
{ label: 'yarn', lines: tokenize('yarn add react react-dom') },
|
|
{ label: 'bun', lines: tokenize('bun add react react-dom') },
|
|
]}
|
|
/>
|
|
|
|
<H2 id="reference">Reference</H2>
|
|
<H3 id="usestate">useState(initialState)</H3>
|
|
<p className="prose-p">
|
|
Call{' '}
|
|
<code className="inline-code" dir="ltr">
|
|
useState
|
|
</code>{' '}
|
|
at the top level of your component to declare a state variable. The
|
|
convention is to name state variables like{' '}
|
|
<code className="inline-code" dir="ltr">
|
|
[something, setSomething]
|
|
</code>{' '}
|
|
using array destructuring.
|
|
</p>
|
|
<CodeBlock
|
|
lang="js"
|
|
filename="MyComponent.js"
|
|
lines={EXAMPLES.basic}
|
|
/>
|
|
|
|
<H3 id="parameters">Parameters</H3>
|
|
<table className="api-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Parameter</th>
|
|
<th>Description</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td>
|
|
<code className="inline-code" dir="ltr">
|
|
initialState
|
|
</code>
|
|
</td>
|
|
<td>
|
|
The value you want the state to be initially. It can be a
|
|
value of any type, but there is a special behavior for
|
|
functions: if you pass a function as{' '}
|
|
<code className="inline-code" dir="ltr">
|
|
initialState
|
|
</code>
|
|
, it will be treated as an initializer function. It should be
|
|
pure, should take no arguments, and should return a value of
|
|
any type.
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<H3 id="returns">Returns</H3>
|
|
<p className="prose-p">
|
|
<code className="inline-code" dir="ltr">
|
|
useState
|
|
</code>{' '}
|
|
returns an array with exactly two values:
|
|
</p>
|
|
<ol className="prose-list prose-list-ordered">
|
|
<li className="prose-li">
|
|
The current state. During the first render, it will match the{' '}
|
|
<code className="inline-code" dir="ltr">
|
|
initialState
|
|
</code>{' '}
|
|
you have passed.
|
|
</li>
|
|
<li className="prose-li">
|
|
The{' '}
|
|
<code className="inline-code" dir="ltr">
|
|
set
|
|
</code>{' '}
|
|
function that lets you update the state to a different value and
|
|
trigger a re-render.
|
|
</li>
|
|
</ol>
|
|
|
|
<div className="callout pitfall">
|
|
<span className="callout-label">
|
|
<IconAlertTriangle size={13} /> Pitfall
|
|
</span>
|
|
<p className="prose-p">
|
|
Calling the{' '}
|
|
<code className="inline-code" dir="ltr">
|
|
set
|
|
</code>{' '}
|
|
function does <em>not</em> change the current state in the already
|
|
executing code. It only affects what{' '}
|
|
<code className="inline-code" dir="ltr">
|
|
useState
|
|
</code>{' '}
|
|
will return starting from the next render.
|
|
</p>
|
|
</div>
|
|
|
|
<H2 id="usage">Usage</H2>
|
|
<H3 id="adding-state">Adding state to a component</H3>
|
|
<p className="prose-p">
|
|
Call{' '}
|
|
<code className="inline-code" dir="ltr">
|
|
useState
|
|
</code>{' '}
|
|
at the top level of your component to declare one or more state
|
|
variables.
|
|
</p>
|
|
<CodeBlock lang="js" filename="Counter.js" lines={EXAMPLES.counter} />
|
|
<p className="prose-p">
|
|
In this example, clicking the button increments the counter: React
|
|
stores the next state, renders your component again with the new
|
|
value, and updates the UI.
|
|
</p>
|
|
|
|
<H3 id="updater-functions">
|
|
Updating state based on the previous state
|
|
</H3>
|
|
<p className="prose-p">
|
|
Suppose the age is{' '}
|
|
<code className="inline-code" dir="ltr">
|
|
42
|
|
</code>
|
|
. This handler calls{' '}
|
|
<code className="inline-code" dir="ltr">
|
|
setAge(a => a + 1)
|
|
</code>{' '}
|
|
three times. Here,{' '}
|
|
<code className="inline-code" dir="ltr">
|
|
a => a + 1
|
|
</code>{' '}
|
|
is an updater function. React puts your updater functions in a queue
|
|
and, during the next render, calls them in the same order.
|
|
</p>
|
|
<CodeBlock lang="js" lines={EXAMPLES.updater} />
|
|
|
|
<div className="callout tip">
|
|
<span className="callout-label">
|
|
<IconLightbulb size={13} /> Tip
|
|
</span>
|
|
<p className="prose-p">
|
|
If you find yourself passing several updater functions in a row,
|
|
it is often simpler to compute the next state once and pass the
|
|
value directly.
|
|
</p>
|
|
</div>
|
|
|
|
<H3 id="objects-and-arrays">Updating objects and arrays in state</H3>
|
|
<p className="prose-p">
|
|
You can put objects and arrays into state. In React, state is
|
|
considered read-only, so you should <em>replace</em> it rather than
|
|
mutate your existing objects.
|
|
</p>
|
|
<CodeBlock lang="js" lines={EXAMPLES.object} />
|
|
|
|
<H3 id="avoiding-recreating-state">
|
|
Avoiding recreating the initial state
|
|
</H3>
|
|
<p className="prose-p">
|
|
React saves the initial state once and ignores it on the next
|
|
renders. If you pass the result of calling a function, it will be
|
|
re-created on every render, which can be wasteful. Pass the
|
|
initializer itself instead:
|
|
</p>
|
|
<CodeBlock lang="js" lines={EXAMPLES.initializer} />
|
|
|
|
<H3 id="resetting-state-with-a-key">Resetting state with a key</H3>
|
|
<p className="prose-p">
|
|
You can reset a component's state by passing a different{' '}
|
|
<code className="inline-code" dir="ltr">
|
|
key
|
|
</code>{' '}
|
|
to a component. In this example, the Reset button changes the{' '}
|
|
<code className="inline-code" dir="ltr">
|
|
version
|
|
</code>{' '}
|
|
state variable, which is passed as a{' '}
|
|
<code className="inline-code" dir="ltr">
|
|
key
|
|
</code>{' '}
|
|
to the{' '}
|
|
<code className="inline-code" dir="ltr">
|
|
Form
|
|
</code>
|
|
. When the key changes, React re-creates the{' '}
|
|
<code className="inline-code" dir="ltr">
|
|
Form
|
|
</code>{' '}
|
|
component (and all of its children) from scratch, so its state gets
|
|
reset.
|
|
</p>
|
|
<CodeBlock lang="js" filename="App.js" lines={EXAMPLES.key} />
|
|
|
|
<Collapsible
|
|
summary="Deep dive: how batching interacts with updater functions"
|
|
defaultOpen={false}
|
|
>
|
|
<p className="prose-p">
|
|
React processes state updates after event handlers have finished
|
|
running. This is called batching. If you need to update the same
|
|
state variable multiple times before the next render, you can pass
|
|
updater functions, which are queued and applied in order during
|
|
the next render.
|
|
</p>
|
|
<p className="prose-p">
|
|
Updater functions must be pure: they run during rendering, so they
|
|
should only calculate the next state and return it, without side
|
|
effects. In Strict Mode, React runs each updater function twice to
|
|
help you find impurities.
|
|
</p>
|
|
</Collapsible>
|
|
|
|
<H3 id="storing-functions">Storing a function in state</H3>
|
|
<p className="prose-p">
|
|
If you want to store a function in state, you have to wrap it in an
|
|
arrow function on both sides, because functions passed to{' '}
|
|
<code className="inline-code" dir="ltr">
|
|
useState
|
|
</code>{' '}
|
|
are otherwise treated as initializers and functions passed to{' '}
|
|
<code className="inline-code" dir="ltr">
|
|
set
|
|
</code>{' '}
|
|
functions are treated as updaters.
|
|
</p>
|
|
<CodeBlock
|
|
lang="js"
|
|
lines={tokenize(`const [fn, setFn] = useState(() => someFunction);
|
|
|
|
function handleClick() {
|
|
setFn(() => someOtherFunction);
|
|
}`)}
|
|
/>
|
|
|
|
<H2 id="caveats">Caveats</H2>
|
|
<ul className="prose-list">
|
|
<li className="prose-li">
|
|
<code className="inline-code" dir="ltr">
|
|
useState
|
|
</code>{' '}
|
|
is a Hook, so you can only call it at the top level of your
|
|
component or your own Hooks. You can't call it inside loops or
|
|
conditions. If you need that, extract a new component and move the
|
|
state into it.
|
|
</li>
|
|
<li className="prose-li">
|
|
In Strict Mode, React will call your initializer function twice in
|
|
order to help you find accidental impurities. This is
|
|
development-only behavior and does not affect production. If your
|
|
initializer function is pure (as it should be), this should not
|
|
affect the behavior.
|
|
</li>
|
|
<li className="prose-li">
|
|
The{' '}
|
|
<code className="inline-code" dir="ltr">
|
|
set
|
|
</code>{' '}
|
|
function only updates the state variable for the next render. If
|
|
you read the state variable after calling the{' '}
|
|
<code className="inline-code" dir="ltr">
|
|
set
|
|
</code>{' '}
|
|
function, you will still get the old value that was on the screen
|
|
before your call.
|
|
</li>
|
|
<li className="prose-li">
|
|
If the new value you provide is identical to the current state, as
|
|
determined by an{' '}
|
|
<code className="inline-code" dir="ltr">
|
|
Object.is
|
|
</code>{' '}
|
|
comparison, React will skip re-rendering the component and its
|
|
children. This is an optimization.
|
|
</li>
|
|
<li className="prose-li">
|
|
React batches state updates. It updates the screen after all the
|
|
event handlers have run and have called their{' '}
|
|
<code className="inline-code" dir="ltr">
|
|
set
|
|
</code>{' '}
|
|
functions. This prevents multiple re-renders during a single
|
|
event. In the rare case that you need to force React to update the
|
|
screen earlier, for example to access the DOM, you can use{' '}
|
|
<code className="inline-code" dir="ltr">
|
|
flushSync
|
|
</code>
|
|
.
|
|
</li>
|
|
</ul>
|
|
|
|
<div className="callout">
|
|
<span className="callout-label">
|
|
<IconInfo size={13} /> Note
|
|
</span>
|
|
<p className="prose-p">
|
|
React uses{' '}
|
|
<code className="inline-code" dir="ltr">
|
|
Object.is
|
|
</code>{' '}
|
|
to compare state values. If the next state is equal to the
|
|
previous state, the update is skipped and your component is not
|
|
re-rendered.
|
|
</p>
|
|
</div>
|
|
|
|
<H2 id="troubleshooting">Troubleshooting</H2>
|
|
<H3 id="updated-but-logging-old">
|
|
I've updated the state, but logging gives me the old value
|
|
</H3>
|
|
<p className="prose-p">
|
|
Calling the{' '}
|
|
<code className="inline-code" dir="ltr">
|
|
set
|
|
</code>{' '}
|
|
function does not change state in the running code, because state
|
|
behaves like a snapshot. If you need to use the next state, you can
|
|
save it in a variable before passing it to the{' '}
|
|
<code className="inline-code" dir="ltr">
|
|
set
|
|
</code>{' '}
|
|
function.
|
|
</p>
|
|
<H3 id="updated-but-screen-not-updating">
|
|
I've updated the state, but the screen doesn't update
|
|
</H3>
|
|
<p className="prose-p">
|
|
React ignores your update if the next state is equal to the previous
|
|
state, as determined by an{' '}
|
|
<code className="inline-code" dir="ltr">
|
|
Object.is
|
|
</code>{' '}
|
|
comparison. This usually happens when you change an object or an
|
|
array in state directly.
|
|
</p>
|
|
<CodeBlock
|
|
lang="js"
|
|
lines={tokenize(`obj.x = 10; // Wrong: mutating existing object
|
|
setObj(obj); // Doesn't do anything
|
|
|
|
// Instead, replace it with a new object:
|
|
setObj({
|
|
...obj,
|
|
x: 10
|
|
});`)}
|
|
/>
|
|
<H3 id="too-many-re-renders">
|
|
I'm getting an error: "Too many re-renders"
|
|
</H3>
|
|
<p className="prose-p">
|
|
You might get an error that says:{' '}
|
|
<em>
|
|
Too many re-renders. React limits the number of renders to prevent
|
|
an infinite loop.
|
|
</em>{' '}
|
|
Typically, this means that you're unconditionally setting state
|
|
during render, so your component enters a loop: render, set state
|
|
(which causes a render), and so on. Very often, this is caused by a
|
|
mistake in specifying an event handler:
|
|
</p>
|
|
<CodeBlock
|
|
lang="js"
|
|
lines={tokenize(`// Wrong: calls the handler during render
|
|
return <button onClick={handleClick()}>Click me</button>
|
|
|
|
// Correct: passes the handler down
|
|
return <button onClick={handleClick}>Click me</button>
|
|
|
|
// Correct: passes an inline function
|
|
return <button onClick={() => handleClick()}>Click me</button>`)}
|
|
/>
|
|
<H3 id="initializer-runs-twice">
|
|
My initializer or updater function runs twice
|
|
</H3>
|
|
<p className="prose-p">
|
|
In Strict Mode, React will call some of your functions twice instead
|
|
of once. This is development-only behavior that helps you keep
|
|
components pure. React uses the result of one of the calls and
|
|
ignores the result of the other call. As long as your component,
|
|
initializer, and updater functions are pure, this shouldn't affect
|
|
your logic.
|
|
</p>
|
|
|
|
<div className="callout">
|
|
<span className="callout-label">
|
|
<IconInfo size={13} /> Note
|
|
</span>
|
|
<p className="prose-p">
|
|
Content adapted from the react.dev{' '}
|
|
<code className="inline-code" dir="ltr">
|
|
useState
|
|
</code>{' '}
|
|
API reference, licensed CC BY 4.0.
|
|
</p>
|
|
</div>
|
|
|
|
<Feedback prompt="Was this page helpful?" />
|
|
<DocsPager
|
|
prev={{ title: 'useRef', href: '#' }}
|
|
next={{ title: 'useSyncExternalStore', href: '#' }}
|
|
prevIcon={<IconArrowLeft size={14} />}
|
|
nextIcon={<IconArrowRight size={14} />}
|
|
/>
|
|
<footer className="docs-footer">
|
|
<a href="#">
|
|
<IconGithub size={13} /> Edit on GitHub
|
|
</a>
|
|
<a href="#">
|
|
<IconDiscord size={13} /> Join the community
|
|
</a>
|
|
<a href="#">
|
|
<IconMessageSquare size={13} /> Discuss on the forum
|
|
</a>
|
|
</footer>
|
|
</article>
|
|
|
|
<nav className="toc" aria-label="On this page">
|
|
<h4>
|
|
<IconList size={13} /> On this page
|
|
</h4>
|
|
<TocScrollspy items={TOC_ITEMS} />
|
|
<ScrollToTop />
|
|
</nav>
|
|
</div>
|
|
</>
|
|
)
|
|
}
|