Repository: reactjs/ja.reactjs.org Branch: main Commit: f97f53da242d Files: 436 Total size: 3.8 MB Directory structure: gitextract_jl4wrltp/ ├── .claude/ │ ├── agents/ │ │ └── docs-reviewer.md │ ├── settings.json │ └── skills/ │ ├── docs-components/ │ │ └── SKILL.md │ ├── docs-sandpack/ │ │ └── SKILL.md │ ├── docs-voice/ │ │ └── SKILL.md │ ├── docs-writer-blog/ │ │ └── SKILL.md │ ├── docs-writer-learn/ │ │ └── SKILL.md │ ├── docs-writer-reference/ │ │ └── SKILL.md │ ├── react-expert/ │ │ └── SKILL.md │ └── review-docs/ │ └── SKILL.md ├── .eslintignore ├── .eslintrc ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── 0-bug.yml │ │ ├── 1-typo.yml │ │ ├── 2-suggestion.yml │ │ ├── 3-framework.yml │ │ └── config.yml │ ├── PULL_REQUEST_TEMPLATE.md │ ├── dependabot.yml │ └── workflows/ │ ├── analyze.yml │ ├── analyze_comment.yml │ ├── discord_notify.yml │ ├── label_core_team_prs.yml │ └── site_lint.yml ├── .gitignore ├── .husky/ │ └── pre-commit ├── .prettierignore ├── .prettierrc ├── CLAUDE.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE-DOCS.md ├── README.md ├── colors.js ├── eslint-local-rules/ │ ├── __tests__/ │ │ ├── fixtures/ │ │ │ └── src/ │ │ │ └── content/ │ │ │ ├── basic-error.md │ │ │ ├── duplicate-metadata.md │ │ │ ├── malformed-metadata.md │ │ │ ├── mixed-language.md │ │ │ ├── stale-expected-error.md │ │ │ └── suppressed-error.md │ │ └── lint-markdown-code-blocks.test.js │ ├── index.js │ ├── package.json │ ├── parser.js │ └── rules/ │ ├── diagnostics.js │ ├── lint-markdown-code-blocks.js │ ├── markdown.js │ ├── metadata.js │ └── react-compiler.js ├── next-env.d.ts ├── next.config.js ├── package.json ├── plugins/ │ ├── gatsby-remark-japanese-fix/ │ │ ├── index.js │ │ └── package.json │ ├── markdownToHtml.js │ ├── remark-header-custom-ids.js │ └── remark-smartypants.js ├── postcss.config.js ├── public/ │ ├── .well-known/ │ │ └── atproto-did │ ├── browserconfig.xml │ ├── html/ │ │ └── single-file-example.html │ ├── js/ │ │ ├── jsfiddle-integration-babel.js │ │ └── jsfiddle-integration.js │ ├── robots.txt │ └── site.webmanifest ├── scripts/ │ ├── copyright.js │ ├── deadLinkChecker.js │ ├── downloadFonts.mjs │ ├── generateRss.js │ ├── headingIDHelpers/ │ │ ├── generateHeadingIDs.js │ │ ├── validateHeadingIDs.js │ │ └── walk.js │ └── headingIdLinter.js ├── src/ │ ├── components/ │ │ ├── Breadcrumbs.tsx │ │ ├── Button.tsx │ │ ├── ButtonLink.tsx │ │ ├── DocsFooter.tsx │ │ ├── ErrorDecoderContext.tsx │ │ ├── ExternalLink.tsx │ │ ├── Icon/ │ │ │ ├── IconArrow.tsx │ │ │ ├── IconArrowSmall.tsx │ │ │ ├── IconBsky.tsx │ │ │ ├── IconCanary.tsx │ │ │ ├── IconChevron.tsx │ │ │ ├── IconClose.tsx │ │ │ ├── IconCodeBlock.tsx │ │ │ ├── IconCopy.tsx │ │ │ ├── IconDeepDive.tsx │ │ │ ├── IconDownload.tsx │ │ │ ├── IconError.tsx │ │ │ ├── IconExperimental.tsx │ │ │ ├── IconFacebookCircle.tsx │ │ │ ├── IconGitHub.tsx │ │ │ ├── IconHamburger.tsx │ │ │ ├── IconHint.tsx │ │ │ ├── IconInstagram.tsx │ │ │ ├── IconLink.tsx │ │ │ ├── IconNavArrow.tsx │ │ │ ├── IconNewPage.tsx │ │ │ ├── IconNote.tsx │ │ │ ├── IconPitfall.tsx │ │ │ ├── IconRestart.tsx │ │ │ ├── IconRocket.tsx │ │ │ ├── IconRss.tsx │ │ │ ├── IconSearch.tsx │ │ │ ├── IconSolution.tsx │ │ │ ├── IconTerminal.tsx │ │ │ ├── IconThreads.tsx │ │ │ ├── IconTwitter.tsx │ │ │ └── IconWarning.tsx │ │ ├── Layout/ │ │ │ ├── Footer.tsx │ │ │ ├── HomeContent.js │ │ │ ├── Page.tsx │ │ │ ├── Sidebar/ │ │ │ │ ├── SidebarButton.tsx │ │ │ │ ├── SidebarLink.tsx │ │ │ │ ├── SidebarRouteTree.tsx │ │ │ │ └── index.tsx │ │ │ ├── SidebarNav/ │ │ │ │ ├── SidebarNav.tsx │ │ │ │ └── index.tsx │ │ │ ├── Toc.tsx │ │ │ ├── TopNav/ │ │ │ │ ├── BrandMenu.tsx │ │ │ │ ├── TopNav.tsx │ │ │ │ └── index.tsx │ │ │ ├── getRouteMeta.tsx │ │ │ └── useTocHighlight.tsx │ │ ├── Logo.tsx │ │ ├── MDX/ │ │ │ ├── BlogCard.tsx │ │ │ ├── Challenges/ │ │ │ │ ├── Challenge.tsx │ │ │ │ ├── Challenges.tsx │ │ │ │ ├── Navigation.tsx │ │ │ │ └── index.tsx │ │ │ ├── CodeBlock/ │ │ │ │ ├── CodeBlock.tsx │ │ │ │ └── index.tsx │ │ │ ├── CodeDiagram.tsx │ │ │ ├── ConsoleBlock.tsx │ │ │ ├── Diagram.tsx │ │ │ ├── DiagramGroup.tsx │ │ │ ├── ErrorDecoder.tsx │ │ │ ├── ExpandableCallout.tsx │ │ │ ├── ExpandableExample.tsx │ │ │ ├── Heading.tsx │ │ │ ├── InlineCode.tsx │ │ │ ├── Intro.tsx │ │ │ ├── LanguagesContext.tsx │ │ │ ├── Link.tsx │ │ │ ├── MDXComponents.module.css │ │ │ ├── MDXComponents.tsx │ │ │ ├── PackageImport.tsx │ │ │ ├── Recap.tsx │ │ │ ├── Sandpack/ │ │ │ │ ├── ClearButton.tsx │ │ │ │ ├── Console.tsx │ │ │ │ ├── CustomPreset.tsx │ │ │ │ ├── DownloadButton.tsx │ │ │ │ ├── ErrorMessage.tsx │ │ │ │ ├── LoadingOverlay.tsx │ │ │ │ ├── NavigationBar.tsx │ │ │ │ ├── OpenInCodeSandboxButton.tsx │ │ │ │ ├── OpenInTypeScriptPlayground.tsx │ │ │ │ ├── Preview.tsx │ │ │ │ ├── ReloadButton.tsx │ │ │ │ ├── SandpackRoot.tsx │ │ │ │ ├── Themes.tsx │ │ │ │ ├── createFileMap.ts │ │ │ │ ├── index.tsx │ │ │ │ ├── runESLint.tsx │ │ │ │ ├── template.ts │ │ │ │ └── useSandpackLint.tsx │ │ │ ├── SandpackWithHTMLOutput.tsx │ │ │ ├── SimpleCallout.tsx │ │ │ ├── TeamMember.tsx │ │ │ ├── TerminalBlock.tsx │ │ │ ├── TocContext.tsx │ │ │ └── YouWillLearnCard.tsx │ │ ├── PageHeading.tsx │ │ ├── Search.tsx │ │ ├── Seo.tsx │ │ ├── SocialBanner.tsx │ │ └── Tag.tsx │ ├── content/ │ │ ├── blog/ │ │ │ ├── 2020/ │ │ │ │ └── 12/ │ │ │ │ └── 21/ │ │ │ │ └── data-fetching-with-react-server-components.md │ │ │ ├── 2021/ │ │ │ │ ├── 06/ │ │ │ │ │ └── 08/ │ │ │ │ │ └── the-plan-for-react-18.md │ │ │ │ └── 12/ │ │ │ │ └── 17/ │ │ │ │ └── react-conf-2021-recap.md │ │ │ ├── 2022/ │ │ │ │ ├── 03/ │ │ │ │ │ ├── 08/ │ │ │ │ │ │ └── react-18-upgrade-guide.md │ │ │ │ │ └── 29/ │ │ │ │ │ └── react-v18.md │ │ │ │ └── 06/ │ │ │ │ └── 15/ │ │ │ │ └── react-labs-what-we-have-been-working-on-june-2022.md │ │ │ ├── 2023/ │ │ │ │ ├── 03/ │ │ │ │ │ ├── 16/ │ │ │ │ │ │ └── introducing-react-dev.md │ │ │ │ │ └── 22/ │ │ │ │ │ └── react-labs-what-we-have-been-working-on-march-2023.md │ │ │ │ └── 05/ │ │ │ │ └── 03/ │ │ │ │ └── react-canaries.md │ │ │ ├── 2024/ │ │ │ │ ├── 02/ │ │ │ │ │ └── 15/ │ │ │ │ │ └── react-labs-what-we-have-been-working-on-february-2024.md │ │ │ │ ├── 04/ │ │ │ │ │ └── 25/ │ │ │ │ │ └── react-19-upgrade-guide.md │ │ │ │ ├── 05/ │ │ │ │ │ └── 22/ │ │ │ │ │ └── react-conf-2024-recap.md │ │ │ │ ├── 10/ │ │ │ │ │ └── 21/ │ │ │ │ │ └── react-compiler-beta-release.md │ │ │ │ └── 12/ │ │ │ │ └── 05/ │ │ │ │ └── react-19.md │ │ │ ├── 2025/ │ │ │ │ ├── 02/ │ │ │ │ │ └── 14/ │ │ │ │ │ └── sunsetting-create-react-app.md │ │ │ │ ├── 04/ │ │ │ │ │ └── 23/ │ │ │ │ │ └── react-labs-view-transitions-activity-and-more.md │ │ │ │ ├── 10/ │ │ │ │ │ ├── 01/ │ │ │ │ │ │ └── react-19-2.md │ │ │ │ │ ├── 07/ │ │ │ │ │ │ ├── introducing-the-react-foundation.md │ │ │ │ │ │ └── react-compiler-1.md │ │ │ │ │ └── 16/ │ │ │ │ │ └── react-conf-2025-recap.md │ │ │ │ └── 12/ │ │ │ │ ├── 03/ │ │ │ │ │ └── critical-security-vulnerability-in-react-server-components.md │ │ │ │ └── 11/ │ │ │ │ └── denial-of-service-and-source-code-exposure-in-react-server-components.md │ │ │ └── index.md │ │ ├── community/ │ │ │ ├── acknowledgements.md │ │ │ ├── conferences.md │ │ │ ├── docs-contributors.md │ │ │ ├── index.md │ │ │ ├── meetups.md │ │ │ ├── team.md │ │ │ ├── translations.md │ │ │ ├── versioning-policy.md │ │ │ └── videos.md │ │ ├── errors/ │ │ │ ├── 377.md │ │ │ ├── generic.md │ │ │ └── index.md │ │ ├── index.md │ │ ├── learn/ │ │ │ ├── add-react-to-an-existing-project.md │ │ │ ├── adding-interactivity.md │ │ │ ├── build-a-react-app-from-scratch.md │ │ │ ├── choosing-the-state-structure.md │ │ │ ├── conditional-rendering.md │ │ │ ├── creating-a-react-app.md │ │ │ ├── describing-the-ui.md │ │ │ ├── editor-setup.md │ │ │ ├── escape-hatches.md │ │ │ ├── extracting-state-logic-into-a-reducer.md │ │ │ ├── importing-and-exporting-components.md │ │ │ ├── index.md │ │ │ ├── installation.md │ │ │ ├── javascript-in-jsx-with-curly-braces.md │ │ │ ├── keeping-components-pure.md │ │ │ ├── lifecycle-of-reactive-effects.md │ │ │ ├── managing-state.md │ │ │ ├── manipulating-the-dom-with-refs.md │ │ │ ├── passing-data-deeply-with-context.md │ │ │ ├── passing-props-to-a-component.md │ │ │ ├── preserving-and-resetting-state.md │ │ │ ├── queueing-a-series-of-state-updates.md │ │ │ ├── react-compiler/ │ │ │ │ ├── debugging.md │ │ │ │ ├── incremental-adoption.md │ │ │ │ ├── index.md │ │ │ │ ├── installation.md │ │ │ │ └── introduction.md │ │ │ ├── react-developer-tools.md │ │ │ ├── reacting-to-input-with-state.md │ │ │ ├── referencing-values-with-refs.md │ │ │ ├── removing-effect-dependencies.md │ │ │ ├── render-and-commit.md │ │ │ ├── rendering-lists.md │ │ │ ├── responding-to-events.md │ │ │ ├── reusing-logic-with-custom-hooks.md │ │ │ ├── scaling-up-with-reducer-and-context.md │ │ │ ├── separating-events-from-effects.md │ │ │ ├── setup.md │ │ │ ├── sharing-state-between-components.md │ │ │ ├── state-a-components-memory.md │ │ │ ├── state-as-a-snapshot.md │ │ │ ├── synchronizing-with-effects.md │ │ │ ├── thinking-in-react.md │ │ │ ├── tutorial-tic-tac-toe.md │ │ │ ├── typescript.md │ │ │ ├── understanding-your-ui-as-a-tree.md │ │ │ ├── updating-arrays-in-state.md │ │ │ ├── updating-objects-in-state.md │ │ │ ├── writing-markup-with-jsx.md │ │ │ ├── you-might-not-need-an-effect.md │ │ │ └── your-first-component.md │ │ ├── reference/ │ │ │ ├── dev-tools/ │ │ │ │ └── react-performance-tracks.md │ │ │ ├── eslint-plugin-react-hooks/ │ │ │ │ ├── index.md │ │ │ │ └── lints/ │ │ │ │ ├── component-hook-factories.md │ │ │ │ ├── config.md │ │ │ │ ├── error-boundaries.md │ │ │ │ ├── exhaustive-deps.md │ │ │ │ ├── gating.md │ │ │ │ ├── globals.md │ │ │ │ ├── immutability.md │ │ │ │ ├── incompatible-library.md │ │ │ │ ├── preserve-manual-memoization.md │ │ │ │ ├── purity.md │ │ │ │ ├── refs.md │ │ │ │ ├── rules-of-hooks.md │ │ │ │ ├── set-state-in-effect.md │ │ │ │ ├── set-state-in-render.md │ │ │ │ ├── static-components.md │ │ │ │ ├── unsupported-syntax.md │ │ │ │ └── use-memo.md │ │ │ ├── react/ │ │ │ │ ├── Activity.md │ │ │ │ ├── Children.md │ │ │ │ ├── Component.md │ │ │ │ ├── Fragment.md │ │ │ │ ├── Profiler.md │ │ │ │ ├── PureComponent.md │ │ │ │ ├── StrictMode.md │ │ │ │ ├── Suspense.md │ │ │ │ ├── ViewTransition.md │ │ │ │ ├── act.md │ │ │ │ ├── addTransitionType.md │ │ │ │ ├── apis.md │ │ │ │ ├── cache.md │ │ │ │ ├── cacheSignal.md │ │ │ │ ├── captureOwnerStack.md │ │ │ │ ├── cloneElement.md │ │ │ │ ├── components.md │ │ │ │ ├── createContext.md │ │ │ │ ├── createElement.md │ │ │ │ ├── createRef.md │ │ │ │ ├── experimental_taintObjectReference.md │ │ │ │ ├── experimental_taintUniqueValue.md │ │ │ │ ├── forwardRef.md │ │ │ │ ├── hooks.md │ │ │ │ ├── index.md │ │ │ │ ├── isValidElement.md │ │ │ │ ├── lazy.md │ │ │ │ ├── legacy.md │ │ │ │ ├── memo.md │ │ │ │ ├── startTransition.md │ │ │ │ ├── use.md │ │ │ │ ├── useActionState.md │ │ │ │ ├── useCallback.md │ │ │ │ ├── useContext.md │ │ │ │ ├── useDebugValue.md │ │ │ │ ├── useDeferredValue.md │ │ │ │ ├── useEffect.md │ │ │ │ ├── useEffectEvent.md │ │ │ │ ├── useId.md │ │ │ │ ├── useImperativeHandle.md │ │ │ │ ├── useInsertionEffect.md │ │ │ │ ├── useLayoutEffect.md │ │ │ │ ├── useMemo.md │ │ │ │ ├── useOptimistic.md │ │ │ │ ├── useReducer.md │ │ │ │ ├── useRef.md │ │ │ │ ├── useState.md │ │ │ │ ├── useSyncExternalStore.md │ │ │ │ └── useTransition.md │ │ │ ├── react-compiler/ │ │ │ │ ├── compilationMode.md │ │ │ │ ├── compiling-libraries.md │ │ │ │ ├── configuration.md │ │ │ │ ├── directives/ │ │ │ │ │ ├── use-memo.md │ │ │ │ │ └── use-no-memo.md │ │ │ │ ├── directives.md │ │ │ │ ├── gating.md │ │ │ │ ├── logger.md │ │ │ │ ├── panicThreshold.md │ │ │ │ └── target.md │ │ │ ├── react-dom/ │ │ │ │ ├── client/ │ │ │ │ │ ├── createRoot.md │ │ │ │ │ ├── hydrateRoot.md │ │ │ │ │ └── index.md │ │ │ │ ├── components/ │ │ │ │ │ ├── common.md │ │ │ │ │ ├── form.md │ │ │ │ │ ├── index.md │ │ │ │ │ ├── input.md │ │ │ │ │ ├── link.md │ │ │ │ │ ├── meta.md │ │ │ │ │ ├── option.md │ │ │ │ │ ├── progress.md │ │ │ │ │ ├── script.md │ │ │ │ │ ├── select.md │ │ │ │ │ ├── style.md │ │ │ │ │ ├── textarea.md │ │ │ │ │ └── title.md │ │ │ │ ├── createPortal.md │ │ │ │ ├── flushSync.md │ │ │ │ ├── hooks/ │ │ │ │ │ ├── index.md │ │ │ │ │ └── useFormStatus.md │ │ │ │ ├── index.md │ │ │ │ ├── preconnect.md │ │ │ │ ├── prefetchDNS.md │ │ │ │ ├── preinit.md │ │ │ │ ├── preinitModule.md │ │ │ │ ├── preload.md │ │ │ │ ├── preloadModule.md │ │ │ │ ├── server/ │ │ │ │ │ ├── index.md │ │ │ │ │ ├── renderToPipeableStream.md │ │ │ │ │ ├── renderToReadableStream.md │ │ │ │ │ ├── renderToStaticMarkup.md │ │ │ │ │ ├── renderToString.md │ │ │ │ │ ├── resume.md │ │ │ │ │ └── resumeToPipeableStream.md │ │ │ │ └── static/ │ │ │ │ ├── index.md │ │ │ │ ├── prerender.md │ │ │ │ ├── prerenderToNodeStream.md │ │ │ │ ├── resumeAndPrerender.md │ │ │ │ └── resumeAndPrerenderToNodeStream.md │ │ │ ├── rsc/ │ │ │ │ ├── directives.md │ │ │ │ ├── server-components.md │ │ │ │ ├── server-functions.md │ │ │ │ ├── use-client.md │ │ │ │ └── use-server.md │ │ │ └── rules/ │ │ │ ├── components-and-hooks-must-be-pure.md │ │ │ ├── index.md │ │ │ ├── react-calls-components-and-hooks.md │ │ │ └── rules-of-hooks.md │ │ ├── versions.md │ │ └── warnings/ │ │ ├── invalid-aria-prop.md │ │ ├── invalid-hook-call-warning.md │ │ ├── react-dom-test-utils.md │ │ ├── react-test-renderer.md │ │ ├── special-props.md │ │ └── unknown-prop.md │ ├── css/ │ │ └── ja-fix.css │ ├── hooks/ │ │ └── usePendingRoute.ts │ ├── pages/ │ │ ├── 404.js │ │ ├── 500.js │ │ ├── [[...markdownPath]].js │ │ ├── _app.tsx │ │ ├── _document.tsx │ │ ├── api/ │ │ │ └── md/ │ │ │ └── [...path].ts │ │ ├── errors/ │ │ │ ├── [errorCode].tsx │ │ │ └── index.tsx │ │ └── llms.txt.tsx │ ├── sidebarBlog.json │ ├── sidebarCommunity.json │ ├── sidebarHome.json │ ├── sidebarLearn.json │ ├── sidebarReference.json │ ├── siteConfig.js │ ├── styles/ │ │ ├── algolia.css │ │ ├── index.css │ │ └── sandpack.css │ ├── types/ │ │ └── docsearch-react-modal.d.ts │ └── utils/ │ ├── analytics.ts │ ├── compileMDX.ts │ ├── finishedTranslations.ts │ ├── forwardRefWithAs.tsx │ ├── prepareMDX.js │ ├── processShim.js │ ├── rafShim.js │ ├── rss.js │ └── toCommaSeparatedList.tsx ├── tailwind.config.js ├── textlint/ │ ├── .gitignore │ ├── .textlintrc.json │ ├── jp-reactjs-org-lint.js │ ├── package.json │ └── prh.yml ├── tsconfig.json └── vercel.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .claude/agents/docs-reviewer.md ================================================ --- name: docs-reviewer description: "Lean docs reviewer that dispatches reviews docs for a particular skill." model: opus color: cyan --- You are a direct, critical, expert reviewer for React documentation. Your role is to use given skills to validate given doc pages for consistency, correctness, and adherence to established patterns. Complete this process: ## Phase 1: Task Creation 1. CRITICAL: Read the skill requested. 2. Understand the skill's requirements. 3. Create a task list to validate skills requirements. ## Phase 2: Validate 1. Read the docs files given. 2. Review each file with the task list to verify. ## Phase 3: Respond You must respond with a checklist of the issues you identified, and line number. DO NOT respond with passed validations, ONLY respond with the problems. ================================================ FILE: .claude/settings.json ================================================ { "skills": { "suggest": [ { "pattern": "src/content/learn/**/*.md", "skill": "docs-writer-learn" }, { "pattern": "src/content/reference/**/*.md", "skill": "docs-writer-reference" } ] }, "permissions": { "allow": [ "Skill(docs-voice)", "Skill(docs-components)", "Skill(docs-sandpack)", "Skill(docs-writer-learn)", "Skill(docs-writer-reference)", "Bash(yarn lint:*)", "Bash(yarn lint-heading-ids:*)", "Bash(yarn lint:fix:*)", "Bash(yarn tsc:*)", "Bash(yarn check-all:*)", "Bash(yarn fix-headings:*)", "Bash(yarn deadlinks:*)", "Bash(yarn prettier:diff:*)" ] } } ================================================ FILE: .claude/skills/docs-components/SKILL.md ================================================ --- name: docs-components description: Comprehensive MDX component patterns (Note, Pitfall, DeepDive, Recipes, etc.) for all documentation types. Authoritative source for component usage, examples, and heading conventions. --- # MDX Component Patterns ## Quick Reference ### Component Decision Tree | Need | Component | |------|-----------| | Helpful tip or terminology | `` | | Common mistake warning | `` | | Advanced technical explanation | `` | | Canary-only feature | `` or `` | | Server Components only | `` | | Deprecated API | `` | | Experimental/WIP | `` | | Visual diagram | `` | | Multiple related examples | `` | | Interactive code | `` (see `/docs-sandpack`) | | Console error display | `` | | End-of-page exercises | `` (Learn pages only) | ### Heading Level Conventions | Component | Heading Level | |-----------|---------------| | DeepDive title | `####` (h4) | | Titled Pitfall | `#####` (h5) | | Titled Note | `####` (h4) | | Recipe items | `####` (h4) | | Challenge items | `####` (h4) | ### Callout Spacing Rules Callout components (Note, Pitfall, DeepDive) require a **blank line after the opening tag** before content begins. **Never place consecutively:** - `` followed by `` - Combine into one with titled subsections, or separate with prose - `` followed by `` - Combine into one, or separate with prose **Allowed consecutive patterns:** - `` followed by `` - OK for multi-part explorations (see useMemo.md) - `` followed by `` - OK when DeepDive explains "why" behind the Pitfall **Separation content:** Prose paragraphs, code examples (Sandpack), or section headers. **Why:** Consecutive warnings create a "wall of cautions" that overwhelms readers and causes important warnings to be skimmed. **Incorrect:** ```mdx Don't do X. Don't do Y. ``` **Correct - combined:** ```mdx ##### Don't do X {/*pitfall-x*/} Explanation. ##### Don't do Y {/*pitfall-y*/} Explanation. ``` **Correct - separated:** ```mdx Don't do X. This leads to another common mistake: Don't do Y. ``` --- ## `` Important clarifications, conventions, or tips. Less severe than Pitfall. ### Simple Note ```mdx The optimization of caching return values is known as [_memoization_](https://en.wikipedia.org/wiki/Memoization). ``` ### Note with Title Use `####` (h4) heading with an ID. ```mdx #### There is no directive for Server Components. {/*no-directive*/} 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 for Server Functions. ``` ### Version-Specific Note ```mdx Starting in React 19, you can render `` as a provider. In older versions of React, use ``. ``` --- ## `` Common mistakes that cause bugs. Use for errors readers will likely make. ### Simple Pitfall ```mdx We recommend defining components as functions instead of classes. [See how to migrate.](#alternatives) ``` ### Titled Pitfall Use `#####` (h5) heading with an ID. ```mdx ##### Calling different memoized functions will read from different caches. {/*pitfall-different-caches*/} To access the same cache, components must call the same memoized function. ``` ### Pitfall with Wrong/Right Code ```mdx ##### `useFormStatus` will not return status information for a `
` rendered in the same component. {/*pitfall-same-component*/} ```js function Form() { // 🔴 `pending` will never be true const { pending } = useFormStatus(); return
; } ``` Instead call `useFormStatus` from inside a component located inside `
`. ``` --- ## `` Optional deep technical content. **First child must be `####` heading with ID.** ### Standard DeepDive ```mdx #### Is using an updater always preferred? {/*is-updater-preferred*/} You might hear a recommendation to always write code like `setAge(a => a + 1)` if the state you're setting is calculated from the previous state. There's no harm in it, but it's also not always necessary. In most cases, there is no difference between these two approaches. React always makes sure that for intentional user actions, like clicks, the `age` state variable would be updated before the next click. ``` ### Comparison DeepDive For comparing related concepts: ```mdx #### When should I use `cache`, `memo`, or `useMemo`? {/*cache-memo-usememo*/} All mentioned APIs offer memoization but differ in what they memoize, who can access the cache, and when their cache is invalidated. #### `useMemo` {/*deep-dive-usememo*/} In general, you should use `useMemo` for caching expensive computations in Client Components across renders. #### `cache` {/*deep-dive-cache*/} In general, you should use `cache` in Server Components to memoize work that can be shared across components. ``` --- ## `` Multiple related examples showing variations. Each recipe needs ``. ```mdx #### Counter (number) {/*counter-number*/} In this example, the `count` state variable holds a number. {/* code */} #### Text field (string) {/*text-field-string*/} In this example, the `text` state variable holds a string. {/* code */} ``` **Common titleText/titleId combinations:** - "Basic [hookName] examples" / `examples-basic` - "Examples of [concept]" / `examples-[concept]` - "The difference between [A] and [B]" / `examples-[topic]` --- ## `` End-of-page exercises. **Learn pages only.** Each challenge needs problem + solution Sandpack. ```mdx #### Fix the bug {/*fix-the-bug*/} Problem description... Optional hint text. {/* problem code */} Explanation... {/* solution code */} ``` **Guidelines:** - Only at end of standard Learn pages - No Challenges in chapter intros or tutorials - Each challenge has `####` heading with ID --- ## `` For deprecated APIs. Content should explain what to use instead. ### Page-Level Deprecation ```mdx In React 19, `forwardRef` is no longer necessary. Pass `ref` as a prop instead. `forwardRef` will be deprecated in a future release. Learn more [here](/blog/2024/04/25/react-19#ref-as-a-prop). ``` ### Method-Level Deprecation ```mdx ### `componentWillMount()` {/*componentwillmount*/} This API has been renamed from `componentWillMount` to [`UNSAFE_componentWillMount`.](#unsafe_componentwillmount) Run the [`rename-unsafe-lifecycles` codemod](codemod-link) to automatically update. ``` --- ## `` For APIs that only work with React Server Components. ### Basic RSC ```mdx `cache` is only for use with [React Server Components](/reference/rsc/server-components). ``` ### Extended RSC (for Server Functions) ```mdx Server Functions are for use in [React Server Components](/reference/rsc/server-components). **Note:** Until September 2024, we referred to all Server Functions as "Server Actions". ``` --- ## `` and `` For features only available in Canary releases. ### Canary Wrapper (inline in Intro) ```mdx `` lets you group elements without a wrapper node. Fragments can also accept refs, enabling interaction with underlying DOM nodes. ``` ### CanaryBadge in Section Headings ```mdx ### FragmentInstance {/*fragmentinstance*/} ``` ### CanaryBadge in Props Lists ```mdx * **optional** `ref`: A ref object from `useRef` or callback function. ``` ### CanaryBadge in Caveats ```mdx * If you want to pass `ref` to a Fragment, you can't use the `<>...` syntax. ``` --- ## `` Visual explanations of module dependencies, render trees, or data flow. ```mdx `'use client'` segments the module dependency tree, marking `InspirationGenerator.js` and all dependencies as client-rendered. ``` **Attributes:** - `name`: Diagram identifier (used for image file) - `height`: Height in pixels - `width`: Width in pixels - `alt`: Accessible description of the diagram --- ## `` (Use Sparingly) Numbered callouts in prose. Pairs with code block annotations. ### Syntax In code blocks: ```mdx ```js [[1, 4, "age"], [2, 4, "setAge"], [3, 4, "42"]] import { useState } from 'react'; function MyComponent() { const [age, setAge] = useState(42); } ``` ``` Format: `[[step_number, line_number, "text_to_highlight"], ...]` In prose: ```mdx 1. The current state initially set to the initial value. 2. The `set` function that lets you change it. ``` ### Guidelines - Maximum 2-3 different colors per explanation - Don't highlight every keyword - only key concepts - Use for terms in prose, not entire code blocks - Maintain consistent usage within a section ✅ **Good use** - highlighting key concepts: ```mdx React will compare the dependencies with the dependencies you passed... ``` 🚫 **Avoid** - excessive highlighting: ```mdx When an Activity boundary is hidden during its initial render... ``` --- ## `` Display console output (errors, warnings, logs). ```mdx Uncaught Error: Too many re-renders. ``` **Levels:** `error`, `warning`, `info` --- ## Component Usage by Page Type ### Reference Pages For component placement rules specific to Reference pages, invoke `/docs-writer-reference`. Key placement patterns: - `` goes before `` at top of page - `` goes after `` for page-level deprecation - `` goes after method heading for method-level deprecation - `` wrapper goes inline within `` - `` appears in headings, props lists, and caveats ### Learn Pages For Learn page structure and patterns, invoke `/docs-writer-learn`. Key usage patterns: - Challenges only at end of standard Learn pages - No Challenges in chapter intros or tutorials - DeepDive for optional advanced content - CodeStep should be used sparingly ### Blog Pages For Blog page structure and patterns, invoke `/docs-writer-blog`. Key usage patterns: - Generally avoid deep technical components - Note and Pitfall OK for clarifications - Prefer inline explanations over DeepDive --- ## Other Available Components **Version/Status:** ``, ``, ``, ``, `` **Visuals:** ``, ``, ``, ``, `` **Console:** ``, `` **Specialized:** ``, ``, ``, ``, ``, ``, ``, ``, `` See existing docs for usage examples of these components. ================================================ FILE: .claude/skills/docs-sandpack/SKILL.md ================================================ --- name: docs-sandpack description: Use when adding interactive code examples to React docs. --- # Sandpack Patterns ## Quick Start Template Most examples are single-file. Copy this and modify: ```mdx ` ` `js import { useState } from 'react'; export default function Example() { const [value, setValue] = useState(0); return ( ); } ` ` ` ``` --- ## File Naming | Pattern | Usage | |---------|-------| | ` ```js ` | Main file (no prefix) | | ` ```js src/FileName.js ` | Supporting files | | ` ```js src/File.js active ` | Active file (reference pages) | | ` ```js src/data.js hidden ` | Hidden files | | ` ```css ` | CSS styles | | ` ```json package.json ` | External dependencies | **Critical:** Main file must have `export default`. ## Line Highlighting ```mdx ```js {2-4} function Example() { // Lines 2-4 // will be // highlighted return null; } ``` ## Code References (numbered callouts) ```mdx ```js [[1, 4, "age"], [2, 4, "setAge"]] // Creates numbered markers pointing to "age" and "setAge" on line 4 ``` ## Expected Errors (intentionally broken examples) ```mdx ```js {expectedErrors: {'react-compiler': [7]}} // Line 7 shows as expected error ``` ## Multi-File Example ```mdx ```js src/App.js import Gallery from './Gallery.js'; export default function App() { return ; } ``` ```js src/Gallery.js export default function Gallery() { return

Gallery

; } ``` ```css h1 { color: purple; } ``` ``` ## External Dependencies ```mdx ```js import { useImmer } from 'use-immer'; // ... ``` ```json package.json { "dependencies": { "immer": "1.7.3", "use-immer": "0.5.1", "react": "latest", "react-dom": "latest", "react-scripts": "latest" } } ``` ``` ## Code Style in Sandpack (Required) Sandpack examples are held to strict code style standards: 1. **Function declarations** for components (not arrows) 2. **`e`** for event parameters 3. **Single quotes** in JSX 4. **`const`** unless reassignment needed 5. **Spaces in destructuring**: `({ props })` not `({props})` 6. **Two-line createRoot**: separate declaration and render call 7. **Multiline if statements**: always use braces ### Don't Create Hydration Mismatches Sandpack examples must produce the same output on server and client: ```js // 🚫 This will cause hydration warnings export default function App() { const isClient = typeof window !== 'undefined'; return
{isClient ? 'Client' : 'Server'}
; } ``` ### Use Ref for Non-Rendered State ```js // 🚫 Don't trigger re-renders for non-visual state const [mounted, setMounted] = useState(false); useEffect(() => { setMounted(true); }, []); // ✅ Use ref instead const mounted = useRef(false); useEffect(() => { mounted.current = true; }, []); ``` ## forwardRef and memo Patterns ### forwardRef - Use Named Function ```js // ✅ Named function for DevTools display name const MyInput = forwardRef(function MyInput(props, ref) { return ; }); // 🚫 Anonymous loses name const MyInput = forwardRef((props, ref) => { ... }); ``` ### memo - Use Named Function ```js // ✅ Preserves component name const Greeting = memo(function Greeting({ name }) { return

Hello, {name}

; }); ``` ## Line Length - Prose: ~80 characters - Code: ~60-70 characters - Break long lines to avoid horizontal scrolling ## Anti-Patterns | Pattern | Problem | Fix | |---------|---------|-----| | `const Comp = () => {}` | Not standard | `function Comp() {}` | | `onClick={(event) => ...}` | Conflicts with global | `onClick={(e) => ...}` | | `useState` for non-rendered values | Re-renders | Use `useRef` | | Reading `window` during render | Hydration mismatch | Check in useEffect | | Single-line if without braces | Harder to debug | Use multiline with braces | | Chained `createRoot().render()` | Less clear | Two statements | | `//...` without space | Inconsistent | `// ...` with space | | Tabs | Inconsistent | 2 spaces | | `ReactDOM.render` | Deprecated | Use `createRoot` | | Fake package names | Confusing | Use `'./your-storage-layer'` | | `PropsWithChildren` | Outdated | `children?: ReactNode` | | Missing `key` in lists | Warnings | Always include key | ## Additional Code Quality Rules ### Always Include Keys in Lists ```js // ✅ Correct {items.map(item =>
  • {item.name}
  • )} // 🚫 Wrong - missing key {items.map(item =>
  • {item.name}
  • )} ``` ### Use Realistic Import Paths ```js // ✅ Correct - descriptive path import { fetchData } from './your-data-layer'; // 🚫 Wrong - looks like a real npm package import { fetchData } from 'cool-data-lib'; ``` ### Console.log Labels ```js // ✅ Correct - labeled for clarity console.log('User:', user); console.log('Component Stack:', errorInfo.componentStack); // 🚫 Wrong - unlabeled console.log(user); ``` ### Keep Delays Reasonable ```js // ✅ Correct - 1-1.5 seconds setTimeout(() => setLoading(false), 1000); // 🚫 Wrong - too long, feels sluggish setTimeout(() => setLoading(false), 3000); ``` ## Updating Line Highlights When modifying code in examples with line highlights (`{2-4}`), **always update the highlight line numbers** to match the new code. Incorrect line numbers cause rendering crashes. ## File Name Conventions - Capitalize file names for component files: `Gallery.js` not `gallery.js` - After initially explaining files are in `src/`, refer to files by name only: `Gallery.js` not `src/Gallery.js` ## Naming Conventions in Code **Components:** PascalCase - `Profile`, `Avatar`, `TodoList`, `PackingList` **State variables:** Destructured pattern - `const [count, setCount] = useState(0)` - Booleans: `[isOnline, setIsOnline]`, `[isPacked, setIsPacked]` - Status strings: `'typing'`, `'submitting'`, `'success'`, `'error'` **Event handlers:** - `handleClick`, `handleSubmit`, `handleAddTask` **Props for callbacks:** - `onClick`, `onChange`, `onAddTask`, `onSelect` **Custom Hooks:** - `useOnlineStatus`, `useChatRoom`, `useFormInput` **Reducer actions:** - Past tense: `'added'`, `'changed'`, `'deleted'` - Snake_case compounds: `'changed_selection'`, `'sent_message'` **Updater functions:** Single letter - `setCount(n => n + 1)` ### Pedagogical Code Markers **Wrong vs right code:** ```js // 🔴 Avoid: redundant state and unnecessary Effect // ✅ Good: calculated during rendering ``` **Console.log for lifecycle teaching:** ```js console.log('✅ Connecting...'); console.log('❌ Disconnected.'); ``` ### Server/Client Labeling ```js // Server Component async function Notes() { const notes = await db.notes.getAll(); } // Client Component "use client" export default function Expandable({children}) { const [expanded, setExpanded] = useState(false); } ``` ### Bundle Size Annotations ```js import marked from 'marked'; // 35.9K (11.2K gzipped) import sanitizeHtml from 'sanitize-html'; // 206K (63.3K gzipped) ``` --- ## Sandpack Example Guidelines ### Package.json Rules **Include package.json when:** - Using external npm packages (immer, remarkable, leaflet, toastify-js, etc.) - Demonstrating experimental/canary React features - Requiring specific React versions (`react: beta`, `react: 19.0.0-rc-*`) **Omit package.json when:** - Example uses only built-in React features - No external dependencies needed - Teaching basic hooks, state, or components **Always mark package.json as hidden:** ```mdx ```json package.json hidden { "dependencies": { "react": "latest", "react-dom": "latest", "react-scripts": "latest", "immer": "1.7.3" } } ``` ``` **Version conventions:** - Use `"latest"` for stable features - Use exact versions only when compatibility requires it - Include minimal dependencies (just what the example needs) ### Hidden File Patterns **Always hide these file types:** | File Type | Reason | |-----------|--------| | `package.json` | Configuration not the teaching point | | `sandbox.config.json` | Sandbox setup is boilerplate | | `public/index.html` | HTML structure not the focus | | `src/data.js` | When it contains sample/mock data | | `src/api.js` | When showing API usage, not implementation | | `src/styles.css` | When styling is not the lesson | | `src/router.js` | Supporting infrastructure | | `src/actions.js` | Server action implementation details | **Rationale:** - Reduces cognitive load - Keeps focus on the primary concept - Creates cleaner, more focused examples **Example:** ```mdx ```js src/data.js hidden export const items = [ { id: 1, name: 'Item 1' }, { id: 2, name: 'Item 2' }, ]; ``` ``` ### Active File Patterns **Mark as active when:** - File contains the primary teaching concept - Learner should focus on this code first - Component demonstrates the hook/pattern being taught **Effect of the `active` marker:** - Sets initial editor tab focus when Sandpack loads - Signals "this is what you should study" - Works with hidden files to create focused examples **Most common active file:** `src/index.js` or `src/App.js` **Example:** ```mdx ```js src/App.js active // This file will be focused when example loads export default function App() { // ... } ``` ``` ### File Structure Guidelines | Scenario | Structure | Reason | |----------|-----------|--------| | Basic hook usage | Single file | Simple, focused | | Teaching imports | 2-3 files | Shows modularity | | Context patterns | 4-5 files | Realistic structure | | Complex state | 3+ files | Separation of concerns | **Single File Examples (70% of cases):** - Use for simple concepts - 50-200 lines typical - Best for: Counter, text inputs, basic hooks **Multi-File Examples (30% of cases):** - Use when teaching modularity/imports - Use for context patterns (4-5 files) - Use when component is reused **File Naming:** - Main component: `App.js` (capitalized) - Component files: `Gallery.js`, `Button.js` (capitalized) - Data files: `data.js` (lowercase) - Utility files: `utils.js` (lowercase) - Context files: `TasksContext.js` (named after what they provide) ### Code Size Limits - Single file: **<200 lines** - Multi-file total: **150-300 lines** - Main component: **100-150 lines** - Supporting files: **20-40 lines each** ### CSS Guidelines **Always:** - Include minimal CSS for demo interactivity - Use semantic class names (`.panel`, `.button-primary`, `.panel-dark`) - Support light/dark themes when showing UI concepts - Keep CSS visible (never hidden) **Size Guidelines:** - Minimal (5-10 lines): Basic button styling, spacing - Medium (15-30 lines): Panel styling, form layouts - Complex (40+ lines): Only for layout-focused examples ================================================ FILE: .claude/skills/docs-voice/SKILL.md ================================================ --- name: docs-voice description: Use when writing any React documentation. Provides voice, tone, and style rules for all doc types. --- # React Docs Voice & Style ## Universal Rules - **Capitalize React terms** when referring to the React concept in headings or as standalone concepts: - Core: Hook, Effect, State, Context, Ref, Component, Fragment - Concurrent: Transition, Action, Suspense - Server: Server Component, Client Component, Server Function, Server Action - Patterns: Error Boundary - Canary: Activity, View Transition, Transition Type - **In prose:** Use lowercase when paired with descriptors: "state variable", "state updates", "event handler". Capitalize when the concept stands alone or in headings: "State is isolated and private" - General usage stays lowercase: "the page transitions", "takes an action" - **Product names:** ESLint, TypeScript, JavaScript, Next.js (not lowercase) - **Bold** for key concepts: **state variable**, **event handler** - **Italics** for new terms being defined: *event handlers* - **Inline code** for APIs: `useState`, `startTransition`, `` - **Avoid:** "simple", "easy", "just", time estimates - Frame differences as "capabilities" not "advantages/disadvantages" - Avoid passive voice and jargon ## Tone by Page Type | Type | Tone | Example | |------|------|---------| | Learn | Conversational | "Here's what that looks like...", "You might be wondering..." | | Reference | Technical | "Call `useState` at the top level...", "This Hook returns..." | | Blog | Accurate | Focus on facts, not marketing | **Note:** Pitfall and DeepDive components can use slightly more conversational phrasing ("You might wonder...", "It might be tempting...") even in Reference pages, since they're explanatory asides. ## Avoiding Jargon **Pattern:** Explain behavior first, then name it. ✅ "React waits until all code in event handlers runs before processing state updates. This is called *batching*." ❌ "React uses batching to process state updates atomically." **Terms to avoid or explain:** | Jargon | Plain Language | |--------|----------------| | atomic | all-or-nothing, batched together | | idempotent | same inputs, same output | | deterministic | predictable, same result every time | | memoize | remember the result, skip recalculating | | referentially transparent | (avoid - describe the behavior) | | invariant | rule that must always be true | | reify | (avoid - describe what's being created) | **Allowed technical terms in Reference pages:** - "stale closures" - standard JS/React term, can be used in Caveats - "stable identity" - React term for consistent object references across renders - "reactive" - React term for values that trigger re-renders when changed - These don't need explanation in Reference pages (readers are expected to know them) **Use established analogies sparingly—once when introducing a concept, not repeatedly:** | Concept | Analogy | |---------|---------| | Components/React | Kitchen (components as cooks, React as waiter) | | Render phases | Restaurant ordering (trigger/render/commit) | | State batching | Waiter collecting full order before going to kitchen | | State behavior | Snapshot/photograph in time | | State storage | React storing state "on a shelf" | | State purpose | Component's memory | | Pure functions | Recipes (same ingredients → same dish) | | Pure functions | Math formulas (y = 2x) | | Props | Adjustable "knobs" | | Children prop | "Hole" to be filled by parent | | Keys | File names in a folder | | Curly braces in JSX | "Window into JavaScript" | | Declarative UI | Taxi driver (destination, not turn-by-turn) | | Imperative UI | Turn-by-turn navigation | | State structure | Database normalization | | Refs | "Secret pocket" React doesn't track | | Effects/Refs | "Escape hatch" from React | | Context | CSS inheritance / "Teleportation" | | Custom Hooks | Design system | ## Common Prose Patterns **Wrong vs Right code:** ```mdx \`\`\`js // 🚩 Don't mutate state: obj.x = 10; \`\`\` \`\`\`js // ✅ Replace with new object: setObj({ ...obj, x: 10 }); \`\`\` ``` **Table comparisons:** ```mdx | passing a function | calling a function | | `onClick={handleClick}` | `onClick={handleClick()}` | ``` **Linking:** ```mdx [Read about state](/learn/state-a-components-memory) [See `useState` reference](/reference/react/useState) ``` ## Code Style - Prefer JSX over createElement - Use const/let, never var - Prefer named function declarations for top-level functions - Arrow functions for callbacks that need `this` preservation ## Version Documentation When APIs change between versions: ```mdx Starting in React 19, render `` as a provider: \`\`\`js {children} \`\`\` In older versions: \`\`\`js {children} \`\`\` ``` Patterns: - "Starting in React 19..." for new APIs - "In older versions of React..." for legacy patterns ================================================ FILE: .claude/skills/docs-writer-blog/SKILL.md ================================================ --- name: docs-writer-blog description: Use when writing or editing files in src/content/blog/. Provides blog post structure and conventions. --- # Blog Post Writer ## Persona **Voice:** Official React team voice **Tone:** Accurate, professional, forward-looking ## Voice & Style For tone, capitalization, jargon, and prose patterns, invoke `/docs-voice`. --- ## Frontmatter Schema All blog posts use this YAML frontmatter structure: ```yaml --- title: "Title in Quotes" author: Author Name(s) date: YYYY/MM/DD description: One or two sentence summary. --- ``` ### Field Details | Field | Format | Example | |-------|--------|---------| | `title` | Quoted string | `"React v19"`, `"React Conf 2024 Recap"` | | `author` | Unquoted, comma + "and" for multiple | `The React Team`, `Dan Abramov and Lauren Tan` | | `date` | `YYYY/MM/DD` with forward slashes | `2024/12/05` | | `description` | 1-2 sentences, often mirrors intro | Summarizes announcement or content | ### Title Patterns by Post Type | Type | Pattern | Example | |------|---------|---------| | Release | `"React vX.Y"` or `"React X.Y"` | `"React v19"` | | Upgrade | `"React [VERSION] Upgrade Guide"` | `"How to Upgrade to React 18"` | | Labs | `"React Labs: [Topic] – [Month Year]"` | `"React Labs: What We've Been Working On – February 2024"` | | Conf | `"React Conf [YEAR] Recap"` | `"React Conf 2024 Recap"` | | Feature | `"Introducing [Feature]"` or descriptive | `"Introducing react.dev"` | | Security | `"[Severity] Security Vulnerability in [Component]"` | `"Critical Security Vulnerability in React Server Components"` | --- ## Author Byline Immediately after frontmatter, add a byline: ```markdown --- Month DD, YYYY by [Author Name](social-link) --- ``` ### Conventions - Full date spelled out: `December 05, 2024` - Team posts link to `/community/team`: `[The React Team](/community/team)` - Individual authors link to Twitter/X or Bluesky - Multiple authors: Oxford comma before "and" - Followed by horizontal rule `---` **Examples:** ```markdown December 05, 2024 by [The React Team](/community/team) --- ``` ```markdown May 3, 2023 by [Dan Abramov](https://bsky.app/profile/danabra.mov), [Sophie Alpert](https://twitter.com/sophiebits), and [Andrew Clark](https://twitter.com/acdlite) --- ``` --- ## Universal Post Structure All blog posts follow this structure: 1. **Frontmatter** (YAML) 2. **Author byline** with date 3. **Horizontal rule** (`---`) 4. **`` component** (1-3 sentences) 5. **Horizontal rule** (`---`) (optional) 6. **Main content sections** (H2 with IDs) 7. **Closing section** (Changelog, Thanks, etc.) --- ## Post Type Templates ### Major Release Announcement ```markdown --- title: "React vX.Y" author: The React Team date: YYYY/MM/DD description: React X.Y is now available on npm! In this post, we'll give an overview of the new features. --- Month DD, YYYY by [The React Team](/community/team) --- React vX.Y is now available on npm! In our [Upgrade Guide](/blog/YYYY/MM/DD/react-xy-upgrade-guide), we shared step-by-step instructions for upgrading. In this post, we'll give an overview of what's new. - [What's new in React X.Y](#whats-new) - [Improvements](#improvements) - [How to upgrade](#how-to-upgrade) --- ## What's new in React X.Y {/*whats-new*/} ### Feature Name {/*feature-name*/} [Problem this solves. Before/after code examples.] For more information, see the docs for [`Feature`](/reference/react/Feature). --- ## Improvements in React X.Y {/*improvements*/} ### Improvement Name {/*improvement-name*/} [Description of improvement.] --- ## How to upgrade {/*how-to-upgrade*/} See [How to Upgrade to React X.Y](/blog/YYYY/MM/DD/react-xy-upgrade-guide) for step-by-step instructions. --- ## Changelog {/*changelog*/} ### React {/*react*/} * Add `useNewHook` for [purpose]. ([#12345](https://github.com/facebook/react/pull/12345) by [@contributor](https://github.com/contributor)) --- _Thanks to [Name](url) for reviewing this post._ ``` ### Upgrade Guide ```markdown --- title: "React [VERSION] Upgrade Guide" author: Author Name date: YYYY/MM/DD description: Step-by-step instructions for upgrading to React [VERSION]. --- Month DD, YYYY by [Author Name](social-url) --- [Summary of upgrade and what this guide covers.] #### Stepping stone version {/*stepping-stone*/} [If applicable, describe intermediate upgrade steps.] In this post, we will guide you through the steps for upgrading: - [Installing](#installing) - [Codemods](#codemods) - [Breaking changes](#breaking-changes) - [New deprecations](#new-deprecations) --- ## Installing {/*installing*/} ```bash npm install --save-exact react@^X.Y.Z react-dom@^X.Y.Z ``` ## Codemods {/*codemods*/} #### Run all React [VERSION] codemods {/*run-all-codemods*/} ```bash npx codemod@latest react/[VERSION]/migration-recipe ``` ## Breaking changes {/*breaking-changes*/} ### Removed: `apiName` {/*removed-api-name*/} `apiName` was deprecated in [Month YYYY (vX.X.X)](link). ```js // Before [old code] // After [new code] ``` Codemod [description]: ```bash npx codemod@latest react/[VERSION]/codemod-name ``` ## New deprecations {/*new-deprecations*/} ### Deprecated: `apiName` {/*deprecated-api-name*/} [Explanation and migration path.] --- Thanks to [Contributor](link) for reviewing this post. ``` ### React Labs Research Update ```markdown --- title: "React Labs: What We've Been Working On – [Month Year]" author: Author1, Author2, and Author3 date: YYYY/MM/DD description: In React Labs posts, we write about projects in active research and development. --- Month DD, YYYY by [Author1](url), [Author2](url), and [Author3](url) --- In React Labs posts, we write about projects in active research and development. We've made significant progress since our [last update](/blog/previous-labs-post), and we'd like to share our progress. [Optional: Roadmap disclaimer about timelines] --- ## Feature Name {/*feature-name*/} `` is now available in React's Canary channel. [Description of feature, motivation, current status.] ### Subsection {/*subsection*/} [Details, examples, use cases.] --- ## Research Area {/*research-area*/} [Problem space description. Status communication.] This research is still early. We'll share more when we're further along. --- _Thanks to [Reviewer](url) for reviewing this post._ Thanks for reading, and see you in the next update! ``` ### React Conf Recap ```markdown --- title: "React Conf [YEAR] Recap" author: Author1 and Author2 date: YYYY/MM/DD description: Last week we hosted React Conf [YEAR]. In this post, we'll summarize the talks and announcements. --- Month DD, YYYY by [Author1](url) and [Author2](url) --- Last week we hosted React Conf [YEAR] [where we announced [key announcements]]. --- The entire [day 1](youtube-url) and [day 2](youtube-url) streams are available online. ## Day 1 {/*day-1*/} _[Watch the full day 1 stream here.](youtube-url)_ [Description of day 1 opening and keynote highlights.] Watch the full day 1 keynote here: ## Day 2 {/*day-2*/} _[Watch the full day 2 stream here.](youtube-url)_ [Day 2 summary.] ## Q&A {/*q-and-a*/} * [Q&A Title](youtube-url) hosted by [Host](url) ## And more... {/*and-more*/} We also heard talks including: * [Talk Title](youtube-url) by [Speaker](url) ## Thank you {/*thank-you*/} Thank you to all the staff, speakers, and participants who made React Conf [YEAR] possible. See you next time! ``` ### Feature/Tool Announcement ```markdown --- title: "Introducing [Feature Name]" author: Author Name date: YYYY/MM/DD description: Today we are announcing [feature]. In this post, we'll explain [what this post covers]. --- Month DD, YYYY by [Author Name](url) --- Today we are [excited/thrilled] to announce [feature]. [What this means for users.] --- ## tl;dr {/*tldr*/} * Key announcement point with [relevant link](/path). * What users can do now. * Availability or adoption information. ## What is [Feature]? {/*what-is-feature*/} [Explanation of the feature/tool.] ## Why we built this {/*why-we-built-this*/} [Motivation, history, problem being solved.] ## Getting started {/*getting-started*/} To install [feature]: npm install package-name [You can find more documentation here.](/path/to/docs) ## What's next {/*whats-next*/} [Future plans and next steps.] ## Thank you {/*thank-you*/} [Acknowledgments to contributors.] --- Thanks to [Reviewer](url) for reviewing this post. ``` ### Security Announcement ```markdown --- title: "[Severity] Security Vulnerability in [Component]" author: The React Team date: YYYY/MM/DD description: Brief summary of the vulnerability. A fix has been published. We recommend upgrading immediately. --- Month DD, YYYY by [The React Team](/community/team) --- [One or two sentences summarizing the vulnerability.] We recommend upgrading immediately. --- On [date], [researcher] reported a security vulnerability that allows [description]. This vulnerability was disclosed as [CVE-YYYY-NNNNN](https://www.cve.org/CVERecord?id=CVE-YYYY-NNNNN) and is rated CVSS [score]. The vulnerability is present in versions [list] of: * [package-name](https://www.npmjs.com/package/package-name) ## Immediate Action Required {/*immediate-action-required*/} A fix was introduced in versions [linked versions]. Upgrade immediately. ### Affected frameworks {/*affected-frameworks*/} [List of affected frameworks with npm links.] ### Vulnerability overview {/*vulnerability-overview*/} [Technical explanation of the vulnerability.] ## Update Instructions {/*update-instructions*/} ### Framework Name {/*update-framework-name*/} ```bash npm install package@version ``` ## Timeline {/*timeline*/} * **November 29th**: [Researcher] reported the vulnerability. * **December 1st**: Fix was created and validated. * **December 3rd**: Fix published and CVE disclosed. ## Attribution {/*attribution*/} Thank you to [Researcher Name](url) for discovering and reporting this vulnerability. ``` --- ## Heading Conventions ### ID Syntax All headings require IDs using CSS comment syntax: ```markdown ## Heading Text {/*heading-id*/} ``` ### ID Rules - Lowercase - Kebab-case (hyphens for spaces) - Remove special characters (apostrophes, colons, backticks) - Concise but descriptive ### Heading Patterns | Context | Example | |---------|---------| | Feature section | `## New Feature: Automatic Batching {/*new-feature-automatic-batching*/}` | | New hook | `### New hook: \`useActionState\` {/*new-hook-useactionstate*/}` | | API in backticks | `### \`\` {/*activity*/}` | | Removed API | `#### Removed: \`propTypes\` {/*removed-proptypes*/}` | | tl;dr section | `## tl;dr {/*tldr*/}` | --- ## Component Usage Guide ### Blog-Appropriate Components | Component | Usage in Blog | |-----------|---------------| | `` | **Required** - Opening summary after byline | | `` | Callouts, caveats, important clarifications | | `` | Warnings about common mistakes | | `` | Optional technical deep dives (use sparingly) | | `` | CLI/installation commands | | `` | Console error/warning output | | `` | Multi-line console output | | `` | Conference video embeds | | `` | Visual explanations | | `` | Auto-generated table of contents | ### `` Pattern Always wrap opening paragraph: ```markdown React 19 is now available on npm! ``` ### `` Patterns **Simple note:** ```markdown For React Native users, React 18 ships with the New Architecture. ``` **Titled note (H4 inside):** ```markdown #### React 18.3 has also been published {/*react-18-3*/} To help with the upgrade, we've published `react@18.3`... ``` ### `` Pattern ```markdown npm install react@latest react-dom@latest ``` ### `` Pattern ```markdown ``` --- ## Link Patterns ### Internal Links | Type | Pattern | Example | |------|---------|---------| | Blog post | `/blog/YYYY/MM/DD/slug` | `/blog/2024/12/05/react-19` | | API reference | `/reference/react/HookName` | `/reference/react/useState` | | Learn section | `/learn/topic-name` | `/learn/react-compiler` | | Community | `/community/team` | `/community/team` | ### External Links | Type | Pattern | |------|---------| | GitHub PR | `[#12345](https://github.com/facebook/react/pull/12345)` | | GitHub user | `[@username](https://github.com/username)` | | Twitter/X | `[@username](https://x.com/username)` | | Bluesky | `[Name](https://bsky.app/profile/handle)` | | CVE | `[CVE-YYYY-NNNNN](https://www.cve.org/CVERecord?id=CVE-YYYY-NNNNN)` | | npm package | `[package](https://www.npmjs.com/package/package)` | ### "See docs" Pattern ```markdown For more information, see the docs for [`useActionState`](/reference/react/useActionState). ``` --- ## Changelog Format ### Bullet Pattern ```markdown * Add `useTransition` for concurrent rendering. ([#10426](https://github.com/facebook/react/pull/10426) by [@acdlite](https://github.com/acdlite)) * Fix `useReducer` observing incorrect props. ([#22445](https://github.com/facebook/react/pull/22445) by [@josephsavona](https://github.com/josephsavona)) ``` **Structure:** `Verb` + backticked API + description + `([#PR](url) by [@user](url))` **Verbs:** Add, Fix, Remove, Make, Improve, Allow, Deprecate ### Section Organization ```markdown ## Changelog {/*changelog*/} ### React {/*react*/} * [changes] ### React DOM {/*react-dom*/} * [changes] ``` --- ## Acknowledgments Format ### Post-closing Thanks ```markdown --- Thanks to [Name](url), [Name](url), and [Name](url) for reviewing this post. ``` Or italicized: ```markdown _Thanks to [Name](url) for reviewing this post._ ``` ### Update Notes For post-publication updates: ```markdown [Updated content] ----- _Updated January 26, 2026._ ``` --- ## Tone & Length by Post Type | Type | Tone | Length | Key Elements | |------|------|--------|--------------| | Release | Celebratory, informative | Medium-long | Feature overview, upgrade link, changelog | | Upgrade | Instructional, precise | Long | Step-by-step, codemods, breaking changes | | Labs | Transparent, exploratory | Medium | Status updates, roadmap disclaimers | | Conf | Enthusiastic, community-focused | Medium | YouTube embeds, speaker credits | | Feature | Excited, explanatory | Medium | tl;dr, "why", getting started | | Security | Urgent, factual | Short-medium | Immediate action, timeline, CVE | --- ## Do's and Don'ts **Do:** - Focus on facts over marketing - Say "upcoming" explicitly for unreleased features - Include FAQ sections for major announcements - Credit contributors and link to GitHub - Use "we" voice for team posts - Link to upgrade guides from release posts - Include table of contents for long posts - End with acknowledgments **Don't:** - Promise features not yet available - Rewrite history (add update notes instead) - Break existing URLs - Use hyperbolic language ("revolutionary", "game-changing") - Skip the `` component - Forget heading IDs - Use heavy component nesting in blogs - Make time estimates or predictions --- ## Updating Old Posts - Never break existing URLs; add redirects when URLs change - Don't rewrite history; add update notes instead: ```markdown [Updated information] ----- _Updated Month Year._ ``` --- ## Critical Rules 1. **Heading IDs required:** `## Title {/*title-id*/}` 2. **`` required:** Every post starts with `` component 3. **Byline required:** Date + linked author(s) after frontmatter 4. **Date format:** Frontmatter uses `YYYY/MM/DD`, byline uses `Month DD, YYYY` 5. **Link to docs:** New APIs must link to reference documentation 6. **Security posts:** Always include "We recommend upgrading immediately" --- ## Components Reference For complete MDX component patterns, invoke `/docs-components`. Blog posts commonly use: ``, ``, ``, ``, ``, ``, ``, ``. Prefer inline explanations over heavy component usage. ================================================ FILE: .claude/skills/docs-writer-learn/SKILL.md ================================================ --- name: docs-writer-learn description: Use when writing or editing files in src/content/learn/. Provides Learn page structure and tone. --- # Learn Page Writer ## Persona **Voice:** Patient teacher guiding a friend through concepts **Tone:** Conversational, warm, encouraging ## Voice & Style For tone, capitalization, jargon, and prose patterns, invoke `/docs-voice`. ## Page Structure Variants ### 1. Standard Learn Page (Most Common) ```mdx --- title: Page Title --- 1-3 sentences introducing the concept. Use *italics* for new terms. * Learning outcome 1 * Learning outcome 2 * Learning outcome 3-5 ## Section Name {/*section-id*/} Content with Sandpack examples, Pitfalls, Notes, DeepDives... ## Another Section {/*another-section*/} More content... * Summary point 1 * Summary point 2 * Summary points 3-9 #### Challenge title {/*challenge-id*/} Description... Optional guidance (single paragraph) {/* Starting code */} Explanation... {/* Fixed code */} ``` ### 2. Chapter Introduction Page For pages that introduce a chapter (like describing-the-ui.md, managing-state.md): ```mdx * [Sub-page title](/learn/sub-page-name) to learn... * [Another page](/learn/another-page) to learn... ## Preview Section {/*section-id*/} Preview description with mini Sandpack example Read **[Page Title](/learn/sub-page-name)** to learn how to... ## What's next? {/*whats-next*/} Head over to [First Page](/learn/first-page) to start reading this chapter page by page! ``` **Important:** Chapter intro pages do NOT include `` or `` sections. ### 3. Tutorial Page For step-by-step tutorials (like tutorial-tic-tac-toe.md): ```mdx Brief statement of what will be built Alternative learning path offered Table of contents (prose listing of major sections) ## Setup {/*setup*/} ... ## Main Content {/*main-content*/} Progressive code building with ### subsections No YouWillLearn, Recap, or Challenges Ends with ordered list of "extra credit" improvements ``` ### 4. Reference-Style Learn Page For pages with heavy API documentation (like typescript.md): ```mdx * [Link to section](#section-anchor) * [Link to another section](#another-section) ## Sections with ### subsections ## Further learning {/*further-learning*/} No Recap or Challenges ``` ## Heading ID Conventions All headings require IDs in `{/*kebab-case*/}` format: ```markdown ## Section Title {/*section-title*/} ### Subsection Title {/*subsection-title*/} #### DeepDive Title {/*deepdive-title*/} ``` **ID Generation Rules:** - Lowercase everything - Replace spaces with hyphens - Remove apostrophes, quotes - Remove or convert special chars (`:`, `?`, `!`, `.`, parentheses) **Examples:** - "What's React?" → `{/*whats-react*/}` - "Step 1: Create the context" → `{/*step-1-create-the-context*/}` - "Conditional (ternary) operator (? :)" → `{/*conditional-ternary-operator--*/}` ## Teaching Patterns ### Problem-First Teaching Show broken/problematic code BEFORE the solution: 1. Present problematic approach with `// 🔴 Avoid:` comment 2. Explain WHY it's wrong (don't just say it is) 3. Show the solution with `// ✅ Good:` comment 4. Invite experimentation ### Progressive Complexity Build understanding in layers: 1. Show simplest working version 2. Identify limitation or repetition 3. Introduce solution incrementally 4. Show complete solution 5. Invite experimentation: "Try changing..." ### Numbered Step Patterns For multi-step processes: **As section headings:** ```markdown ### Step 1: Action to take {/*step-1-action*/} ### Step 2: Next action {/*step-2-next-action*/} ``` **As inline lists:** ```markdown To implement this: 1. **Declare** `inputRef` with the `useRef` Hook. 2. **Pass it** as ``. 3. **Read** the input DOM node from `inputRef.current`. ``` ### Interactive Invitations After Sandpack examples, encourage experimentation: - "Try changing X to Y. See how...?" - "Try it in the sandbox above!" - "Click each button separately:" - "Have a guess!" - "Verify that..." ### Decision Questions Help readers build intuition: > "When you're not sure whether some code should be in an Effect or in an event handler, ask yourself *why* this code needs to run." ## Component Placement Order 1. `` - First after frontmatter 2. `` - After Intro (standard/chapter pages) 3. Body content with ``, ``, `` placed contextually 4. `` - Before Challenges (standard pages only) 5. `` - End of page (standard pages only) For component structure and syntax, invoke `/docs-components`. ## Code Examples For Sandpack file structure, naming conventions, code style, and pedagogical markers, invoke `/docs-sandpack`. ## Cross-Referencing ### When to Link **Link to /learn:** - Explaining concepts or mental models - Teaching how things work together - Tutorials and guides - "Why" questions **Link to /reference:** - API details, Hook signatures - Parameter lists and return values - Rules and restrictions - "What exactly" questions ### Link Formats ```markdown [concept name](/learn/page-name) [`useState`](/reference/react/useState) [section link](/learn/page-name#section-id) [MDN](https://developer.mozilla.org/...) ``` ## Section Dividers **Important:** Learn pages typically do NOT use `---` dividers. The heading hierarchy provides sufficient structure. Only consider dividers in exceptional cases like separating main content from meta/contribution sections. ## Do's and Don'ts **Do:** - Use "you" to address the reader - Show broken code before fixes - Explain behavior before naming concepts - Build concepts progressively - Include interactive Sandpack examples - Use established analogies consistently - Place Pitfalls AFTER explaining concepts - Invite experimentation with "Try..." phrases **Don't:** - Use "simple", "easy", "just", or time estimates - Reference concepts not yet introduced - Skip required components for page type - Use passive voice without reason - Place Pitfalls before teaching the concept - Use `---` dividers between sections - Create unnecessary abstraction in examples - Place consecutive Pitfalls or Notes without separating prose (combine or separate) ## Critical Rules 1. **All headings require IDs:** `## Title {/*title-id*/}` 2. **Chapter intros use `isChapter={true}` and ``** 3. **Tutorial pages omit YouWillLearn/Recap/Challenges** 4. **Problem-first teaching:** Show broken → explain → fix 5. **No consecutive Pitfalls/Notes:** See `/docs-components` Callout Spacing Rules For component patterns, invoke `/docs-components`. For Sandpack patterns, invoke `/docs-sandpack`. ================================================ FILE: .claude/skills/docs-writer-reference/SKILL.md ================================================ --- name: docs-writer-reference description: Reference page structure, templates, and writing patterns for src/content/reference/. For components, see /docs-components. For code examples, see /docs-sandpack. --- # Reference Page Writer ## Quick Reference ### Page Type Decision Tree 1. Is it a Hook? Use **Type A (Hook/Function)** 2. Is it a React component (``)? Use **Type B (Component)** 3. Is it a compiler configuration option? Use **Type C (Configuration)** 4. Is it a directive (`'use something'`)? Use **Type D (Directive)** 5. Is it an ESLint rule? Use **Type E (ESLint Rule)** 6. Is it listing multiple APIs? Use **Type F (Index/Category)** ### Component Selection For component selection and patterns, invoke `/docs-components`. --- ## Voice & Style **Voice:** Authoritative technical reference writer **Tone:** Precise, comprehensive, neutral For tone, capitalization, jargon, and prose patterns, invoke `/docs-voice`. **Do:** - Start with single-line description: "`useState` is a React Hook that lets you..." - Include Parameters, Returns, Caveats sections for every API - Document edge cases most developers will encounter - Use section dividers between major sections - Include "See more examples below" links - Be assertive, not hedging - "This is designed for..." not "This helps avoid issues with..." - State facts, not benefits - "The callback always accesses the latest values" not "This helps avoid stale closures" - Use minimal but meaningful names - `onEvent` or `onTick` over `onSomething` **Don't:** - Skip the InlineToc component - Omit error cases or caveats - Use conversational language - Mix teaching with reference (that's Learn's job) - Document past bugs or fixed issues - Include niche edge cases (e.g., `this` binding, rare class patterns) - Add phrases explaining "why you'd want this" - the Usage section examples do that - Exception: Pitfall and DeepDive asides can use slightly conversational phrasing --- ## Page Templates ### Type A: Hook/Function **When to use:** Documenting React hooks and standalone functions (useState, useEffect, memo, lazy, etc.) ```mdx --- title: hookName --- `hookName` is a React Hook that lets you [brief description]. ```js const result = hookName(arg) ``` --- ## Reference {/*reference*/} ### `hookName(arg)` {/*hookname*/} Call `hookName` at the top level of your component to... ```js [signature example with annotations] ``` [See more examples below.](#usage) #### Parameters {/*parameters*/} * `arg`: Description of the parameter. #### Returns {/*returns*/} Description of return value. #### Caveats {/*caveats*/} * Important caveat about usage. --- ## Usage {/*usage*/} ### Common Use Case {/*common-use-case*/} Explanation with Sandpack examples... --- ## Troubleshooting {/*troubleshooting*/} ### Common Problem {/*common-problem*/} How to solve it... ``` --- ### Type B: Component **When to use:** Documenting React components (Suspense, Fragment, Activity, StrictMode) ```mdx --- title: --- `` lets you [primary action]. ```js ``` --- ## Reference {/*reference*/} ### `` {/*componentname*/} [Component purpose and behavior] #### Props {/*props*/} * `propName`: Description of the prop... * **optional** `optionalProp`: Description... #### Caveats {/*caveats*/} * [Caveats specific to this component] ``` **Key differences from Hook pages:** - Title uses JSX syntax: `` - Uses `#### Props` instead of `#### Parameters` - Reference heading uses JSX: `` ### `` `` --- ### Type C: Configuration **When to use:** Documenting React Compiler configuration options ```mdx --- title: optionName --- The `optionName` option [controls/specifies/determines] [what it does]. ```js { optionName: 'value' // Quick example } ``` --- ## Reference {/*reference*/} ### `optionName` {/*optionname*/} [Description of the option's purpose] #### Type {/*type*/} ``` 'value1' | 'value2' | 'value3' ``` #### Default value {/*default-value*/} `'value1'` #### Options {/*options*/} - **`'value1'`** (default): Description - **`'value2'`**: Description - **`'value3'`**: Description #### Caveats {/*caveats*/} * [Usage caveats] ``` --- ### Type D: Directive **When to use:** Documenting directives like 'use server', 'use client', 'use memo' ```mdx --- title: "'use directive'" titleForTitleTag: "'use directive' directive" --- `'use directive'` is for use with [React Server Components](/reference/rsc/server-components). `'use directive'` marks [what it marks] for [purpose]. ```js {1} function MyComponent() { 'use directive'; // ... } ``` --- ## Reference {/*reference*/} ### `'use directive'` {/*use-directive*/} Add `'use directive'` at the beginning of [location] to [action]. #### Caveats {/*caveats*/} * `'use directive'` must be at the very beginning... * The directive must be written with single or double quotes, not backticks. * [Other placement/syntax caveats] ``` **Key characteristics:** - Title includes quotes: `title: "'use server'"` - Uses `titleForTitleTag` for browser tab title - `` block appears before `` - Caveats focus on placement and syntax requirements --- ### Type E: ESLint Rule **When to use:** Documenting ESLint plugin rules ```mdx --- title: rule-name --- Validates that [what the rule checks]. ## Rule Details {/*rule-details*/} [Explanation of why this rule exists and React's underlying assumptions] ## Common Violations {/*common-violations*/} [Description of violation patterns] ### Invalid {/*invalid*/} Examples of incorrect code for this rule: ```js // X Missing dependency useEffect(() => { console.log(count); }, []); // Missing 'count' ``` ### Valid {/*valid*/} Examples of correct code for this rule: ```js // checkmark All dependencies included useEffect(() => { console.log(count); }, [count]); ``` ## Troubleshooting {/*troubleshooting*/} ### [Problem description] {/*problem-slug*/} [Solution] ## Options {/*options*/} [Configuration options if applicable] ``` **Key characteristics:** - Intro is a single "Validates that..." sentence - Uses "Invalid"/"Valid" sections with emoji-prefixed code comments - Rule Details explains "why" not just "what" --- ### Type F: Index/Category **When to use:** Overview pages listing multiple APIs in a category ```mdx --- title: "Built-in React [Type]" --- *Concept* let you [purpose]. Brief scope statement. --- ## Category Name {/*category-name*/} *Concept* explanation with [Learn section link](/learn/topic). To [action], use one of these [Type]: * [`apiName`](/reference/react/apiName) lets you [action]. * [`apiName`](/reference/react/apiName) declares [thing]. ```js function Example() { const value = useHookName(args); } ``` --- ## Your own [Type] {/*your-own-type*/} You can also [define your own](/learn/topic) as JavaScript functions. ``` **Key characteristics:** - Title format: "Built-in React [Type]" - Italicized concept definitions - Horizontal rules between sections - Closes with "Your own [Type]" section --- ## Advanced Patterns ### Multi-Function Documentation **When to use:** When a hook returns a function that needs its own documentation (useState's setter, useReducer's dispatch) ```md ### `hookName(args)` {/*hookname*/} [Main hook documentation] #### Parameters {/*parameters*/} #### Returns {/*returns*/} #### Caveats {/*caveats*/} --- ### `set` functions, like `setSomething(nextState)` {/*setstate*/} The `set` function returned by `hookName` lets you [action]. #### Parameters {/*setstate-parameters*/} #### Returns {/*setstate-returns*/} #### Caveats {/*setstate-caveats*/} ``` **Key conventions:** - Horizontal rule (`---`) separates main hook from returned function - Heading IDs include prefix: `{/*setstate-parameters*/}` vs `{/*parameters*/}` - Use generic names: "set functions" not "setCount" --- ### Compound Return Objects **When to use:** When a function returns an object with multiple properties/methods (createContext) ```md ### `createContext(defaultValue)` {/*createcontext*/} [Main function documentation] #### Returns {/*returns*/} `createContext` returns a context object. **The context object itself does not hold any information.** It represents... * `SomeContext` lets you provide the context value. * `SomeContext.Consumer` is an alternative way to read context. --- ### `SomeContext` Provider {/*provider*/} [Documentation for Provider] #### Props {/*provider-props*/} --- ### `SomeContext.Consumer` {/*consumer*/} [Documentation for Consumer] #### Props {/*consumer-props*/} ``` --- ## Writing Patterns ### Opening Lines by Page Type | Page Type | Pattern | Example | |-----------|---------|---------| | Hook | `` `hookName` is a React Hook that lets you [action]. `` | "`useState` is a React Hook that lets you add a state variable to your component." | | Component | `` `` lets you [action]. `` | "`` lets you display a fallback until its children have finished loading." | | API | `` `apiName` lets you [action]. `` | "`memo` lets you skip re-rendering a component when its props are unchanged." | | Configuration | `` The `optionName` option [controls/specifies/determines] [what]. `` | "The `target` option specifies which React version the compiler generates code for." | | Directive | `` `'directive'` [marks/opts/prevents] [what] for [purpose]. `` | "`'use server'` marks a function as callable from the client." | | ESLint Rule | `` Validates that [condition]. `` | "Validates that dependency arrays for React hooks contain all necessary dependencies." | --- ### Parameter Patterns **Simple parameter:** ```md * `paramName`: Description of what it does. ``` **Optional parameter:** ```md * **optional** `paramName`: Description of what it does. ``` **Parameter with special function behavior:** ```md * `initialState`: 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. This argument is ignored after the initial render. * If you pass a function as `initialState`, it will be treated as an _initializer function_. It should be pure, should take no arguments, and should return a value of any type. ``` **Callback parameter with sub-parameters:** ```md * `subscribe`: A function that takes a single `callback` argument and subscribes it to the store. When the store changes, it should invoke the provided `callback`. The `subscribe` function should return a function that cleans up the subscription. ``` **Nested options object:** ```md * **optional** `options`: An object with options for this React root. * **optional** `onCaughtError`: Callback called when React catches an error in an Error Boundary. * **optional** `onUncaughtError`: Callback called when an error is thrown and not caught. * **optional** `identifierPrefix`: A string prefix React uses for IDs generated by `useId`. ``` --- ### Return Value Patterns **Single value return:** ```md `hookName` returns the current value. The value will be the same as `initialValue` during the first render. ``` **Array return (numbered list):** ```md `useState` returns an array with exactly two values: 1. The current state. During the first render, it will match the `initialState` you have passed. 2. The [`set` function](#setstate) that lets you update the state to a different value and trigger a re-render. ``` **Object return (bulleted list):** ```md `createElement` returns a React element object with a few properties: * `type`: The `type` you have passed. * `props`: The `props` you have passed except for `ref` and `key`. * `ref`: The `ref` you have passed. If missing, `null`. * `key`: The `key` you have passed, coerced to a string. If missing, `null`. ``` **Promise return:** ```md `prerender` returns a Promise: - If rendering is successful, the Promise will resolve to an object containing: - `prelude`: a [Web Stream](MDN-link) of HTML. - `postponed`: a JSON-serializable object for resumption. - If rendering fails, the Promise will be rejected. ``` **Wrapped function return:** ```md `cache` returns a cached version of `fn` with the same type signature. It does not call `fn` in the process. When calling `cachedFn` with given arguments, it first checks if a cached result exists. If cached, it returns the result. If not, it calls `fn`, stores the result, and returns it. ``` --- ### Caveats Patterns **Standard Hook caveat (almost always first for Hooks):** ```md * `useXxx` 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. ``` **Stable identity caveat (for returned functions):** ```md * The `set` function has a stable identity, so you will often see it omitted from Effect dependencies, but including it will not cause the Effect to fire. ``` **Strict Mode caveat:** ```md * In Strict Mode, React will **call your render function twice** in order to help you find accidental impurities. This is development-only behavior and does not affect production. ``` **Caveat with code example:** ```md * It's not recommended to _suspend_ a render based on a store value returned by `useSyncExternalStore`. For example, the following is discouraged: ```js const selectedProductId = useSyncExternalStore(...); const data = use(fetchItem(selectedProductId)) // X Don't suspend based on store value ``` ``` **Canary caveat:** ```md * If you want to pass `ref` to a Fragment, you can't use the `<>...` syntax. ``` --- ### Troubleshooting Patterns **Heading format (first person problem statements):** ```md ### I've updated the state, but logging gives me the old value {/*old-value*/} ### My initializer or updater function runs twice {/*runs-twice*/} ### I want to read the latest state from a callback {/*read-latest-state*/} ``` **Error message format:** ```md ### I'm getting an error: "Too many re-renders" {/*too-many-rerenders*/} ### I'm getting an error: "Rendered more hooks than during the previous render" {/*more-hooks*/} ``` **Lint error format:** ```md ### I'm getting a lint error: "[exact error message]" {/*lint-error-slug*/} ``` **Problem-solution structure:** 1. State the problem with code showing the issue 2. Explain why it happens 3. Provide the solution with corrected code 4. Link to Learn section for deeper understanding --- ### Code Comment Conventions For code comment conventions (wrong/right, legacy/recommended, server/client labeling, bundle size annotations), invoke `/docs-sandpack`. --- ### Link Description Patterns | Pattern | Example | |---------|---------| | "lets you" + action | "`memo` lets you skip re-rendering when props are unchanged." | | "declares" + thing | "`useState` declares a state variable that you can update directly." | | "reads" + thing | "`useContext` reads and subscribes to a context." | | "connects" + thing | "`useEffect` connects a component to an external system." | | "Used with" | "Used with [`useContext`.](/reference/react/useContext)" | | "Similar to" | "Similar to [`useTransition`.](/reference/react/useTransition)" | --- ## Component Patterns For comprehensive MDX component patterns (Note, Pitfall, DeepDive, Recipes, Deprecated, RSC, Canary, Diagram, Code Steps), invoke `/docs-components`. For Sandpack-specific patterns and code style, invoke `/docs-sandpack`. ### Reference-Specific Component Rules **Component placement in Reference pages:** - `` goes before `` at top of page - `` goes after `` for page-level deprecation - `` goes after method heading for method-level deprecation - `` wrapper goes inline within `` - `` appears in headings, props lists, and caveats **Troubleshooting-specific components:** - Use first-person problem headings - Cross-reference Pitfall IDs when relevant **Callout spacing:** - Never place consecutive Pitfalls or consecutive Notes - Combine related warnings into one with titled subsections, or separate with prose/code - Consecutive DeepDives OK for multi-part explorations - See `/docs-components` Callout Spacing Rules --- ## Content Principles ### Intro Section - **One sentence, ~15 words max** - State what the Hook does, not how it works - ✅ "`useEffectEvent` is a React Hook that lets you separate events from Effects." - ❌ "`useEffectEvent` is a React Hook that lets you extract non-reactive logic from your Effects into a reusable function called an Effect Event." ### Reference Code Example - Show just the API call (5-10 lines), not a full component - Move full component examples to Usage section ### Usage Section Structure 1. **First example: Core mental model** - Show the canonical use case with simplest concrete example 2. **Subsequent examples: Canonical use cases** - Name the *why* (e.g., "Avoid reconnecting to external systems"), show a concrete *how* - Prefer broad canonical use cases over multiple narrow concrete examples - The section title IS the teaching - "When would I use this?" should be answered by the heading ### What to Include vs. Exclude - **Never** document past bugs or fixed issues - **Include** edge cases most developers will encounter - **Exclude** niche edge cases (e.g., `this` binding, rare class patterns) ### Caveats Section - Include rules the linter enforces or that cause immediate errors - Include fundamental usage restrictions - Exclude implementation details unless they affect usage - Exclude repetition of things explained elsewhere - Keep each caveat to one sentence when possible ### Troubleshooting Section - Error headings only: "I'm getting an error: '[message]'" format - Never document past bugs - if it's fixed, it doesn't belong here - Focus on errors developers will actually encounter today ### DeepDive Content - **Goldilocks principle** - Deep enough for curious developers, short enough to not overwhelm - Answer "why is it designed this way?" - not exhaustive technical details - Readers who skip it should miss nothing essential for using the API - If the explanation is getting long, you're probably explaining too much --- ## Domain-Specific Guidance ### Hooks **Returned function documentation:** - Document setter/dispatch functions as separate `###` sections - Use generic names: "set functions" not "setCount" - Include stable identity caveat for returned functions **Dependency array documentation:** - List what counts as reactive values - Explain when dependencies are ignored - Link to removing effect dependencies guide **Recipes usage:** - Group related examples with meaningful titleText - Each recipe has brief intro, Sandpack, and `` --- ### Components **Props documentation:** - Use `#### Props` instead of `#### Parameters` - Mark optional props with `**optional**` prefix - Use `` inline for canary-only props **JSX syntax in titles/headings:** - Frontmatter title: `title: ` - Reference heading: `` ### `` {/*suspense*/} `` --- ### React-DOM **Common props linking:** ```md `` supports all [common element props.](/reference/react-dom/components/common#common-props) ``` **Props categorization:** - Controlled vs uncontrolled props grouped separately - Form-specific props documented with action patterns - MDN links for standard HTML attributes **Environment-specific notes:** ```mdx This API is specific to Node.js. Environments with [Web Streams](MDN-link), like Deno and modern edge runtimes, should use [`renderToReadableStream`](/reference/react-dom/server/renderToReadableStream) instead. ``` **Progressive enhancement:** - Document benefits for users without JavaScript - Explain Server Function + form action integration - Show hidden form field and `.bind()` patterns --- ### RSC **RSC banner (before Intro):** Always place `` component before `` for Server Component-only APIs. **Serialization type lists:** When documenting Server Function arguments, list supported types: ```md Supported types for Server Function arguments: * Primitives * [string](MDN-link) * [number](MDN-link) * Iterables containing serializable values * [Array](MDN-link) * [Map](MDN-link) Notably, these are not supported: * React elements, or [JSX](/learn/writing-markup-with-jsx) * Functions (other than Server Functions) ``` **Bundle size comparisons:** - Show "Not included in bundle" for server-only imports - Annotate client bundle sizes with gzip: `// 35.9K (11.2K gzipped)` --- ### Compiler **Configuration page structure:** - Type (union type or interface) - Default value - Options/Valid values with descriptions **Directive documentation:** - Placement requirements are critical - Mode interaction tables showing combinations - "Use sparingly" + "Plan for removal" patterns for escape hatches **Library author guides:** - Audience-first intro - Benefits/Why section - Numbered step-by-step setup --- ### ESLint **Rule Details section:** - Explain "why" not just "what" - Focus on React's underlying assumptions - Describe consequences of violations **Invalid/Valid sections:** - Standard intro: "Examples of [in]correct code for this rule:" - Use X emoji for invalid, checkmark for valid - Show inline comments explaining the violation **Configuration options:** - Show shared settings (preferred) - Show rule-level options (backward compatibility) - Note precedence when both exist --- ## Edge Cases For deprecated, canary, and version-specific component patterns (placement, syntax, examples), invoke `/docs-components`. **Quick placement rules:** - `` after `` for page-level, after heading for method-level - `` wrapper inline in Intro, `` in headings/props/caveats - Version notes use `` with "Starting in React 19..." pattern **Removed APIs on index pages:** ```md ## Removed APIs {/*removed-apis*/} These APIs were removed in React 19: * [`render`](https://18.react.dev/reference/react-dom/render): use [`createRoot`](/reference/react-dom/client/createRoot) instead. ``` Link to previous version docs (18.react.dev) for removed API documentation. --- ## Critical Rules 1. **Heading IDs required:** `## Title {/*title-id*/}` (lowercase, hyphens) 2. **Sandpack main file needs `export default`** 3. **Active file syntax:** ` ```js src/File.js active ` 4. **Error headings in Troubleshooting:** Use `### I'm getting an error: "[message]" {/*id*/}` 5. **Section dividers (`---`)** required between headings (see Section Dividers below) 6. **InlineToc required:** Always include `` after Intro 7. **Consistent parameter format:** Use `* \`paramName\`: description` with `**optional**` prefix for optional params 8. **Numbered lists for array returns:** When hooks return arrays, use numbered lists in Returns section 9. **Generic names for returned functions:** Use "set functions" not "setCount" 10. **Props vs Parameters:** Use `#### Props` for Components (Type B), `#### Parameters` for Hooks/APIs (Type A) 11. **RSC placement:** `` component goes before ``, not after 12. **Canary markers:** Use `` wrapper inline in Intro, `` in headings/props 13. **Deprecated placement:** `` goes after `` for page-level, after heading for method-level 14. **Code comment emojis:** Use X for wrong, checkmark for correct in code examples 15. **No consecutive Pitfalls/Notes:** Combine into one component with titled subsections, or separate with prose/code (see `/docs-components`) For component heading level conventions (DeepDive, Pitfall, Note, Recipe headings), see `/docs-components`. ### Section Dividers Use `---` horizontal rules to visually separate major sections: - **After ``** - Before `## Reference` heading - **Between API subsections** - Between different function/hook definitions (e.g., between `useState()` and `set functions`) - **Before `## Usage`** - Separates API reference from examples - **Before `## Troubleshooting`** - Separates content from troubleshooting - **Between EVERY Usage subsections** - When switching to a new major use case Always have a blank line before and after `---`. ### Section ID Conventions | Section | ID Format | |---------|-----------| | Main function | `{/*functionname*/}` | | Returned function | `{/*setstate*/}`, `{/*dispatch*/}` | | Sub-section of returned function | `{/*setstate-parameters*/}` | | Troubleshooting item | `{/*problem-description-slug*/}` | | Pitfall | `{/*pitfall-description*/}` | | Deep dive | `{/*deep-dive-topic*/}` | ================================================ FILE: .claude/skills/react-expert/SKILL.md ================================================ --- name: react-expert description: Use when researching React APIs or concepts for documentation. Use when you need authoritative usage examples, caveats, warnings, or errors for a React feature. --- # React Expert Research Skill ## Overview This skill produces exhaustive documentation research on any React API or concept by searching authoritative sources (tests, source code, PRs, issues) rather than relying on LLM training knowledge. **Skepticism Mandate:** You must be skeptical of your own knowledge. Claude is often trained on outdated or incorrect React patterns. Treat source material as the sole authority. If findings contradict your prior understanding, explicitly flag this discrepancy. **Red Flags - STOP if you catch yourself thinking:** - "I know this API does X" → Find source evidence first - "Common pattern is Y" → Verify in test files - Generating example code → Must have source file reference ## Invocation ``` /react-expert useTransition /react-expert suspense boundaries /react-expert startTransition ``` ## Sources (Priority Order) 1. **React Repo Tests** - Most authoritative for actual behavior 2. **React Source Code** - Warnings, errors, implementation details 3. **Git History** - Commit messages with context 4. **GitHub PRs & Comments** - Design rationale (via `gh` CLI) 5. **GitHub Issues** - Confusion/questions (facebook/react + reactjs/react.dev) 6. **React Working Group** - Design discussions for newer APIs 7. **Flow Types** - Source of truth for type signatures 8. **TypeScript Types** - Note discrepancies with Flow 9. **Current react.dev docs** - Baseline (not trusted as complete) **No web search** - No Stack Overflow, blog posts, or web searches. GitHub API via `gh` CLI is allowed. ## Workflow ### Step 1: Setup React Repo First, ensure the React repo is available locally: ```bash # Check if React repo exists, clone or update if [ -d ".claude/react" ]; then cd .claude/react && git pull origin main else git clone --depth=100 https://github.com/facebook/react.git .claude/react fi ``` Get the current commit hash for the research document: ```bash cd .claude/react && git rev-parse --short HEAD ``` ### Step 2: Dispatch 6 Parallel Research Agents Spawn these agents IN PARALLEL using the Task tool. Each agent receives the skepticism preamble: > "You are researching React's ``. CRITICAL: Do NOT rely on your prior knowledge about this API. Your training may contain outdated or incorrect patterns. Only report what you find in the source files. If your findings contradict common understanding, explicitly highlight this discrepancy." | Agent | subagent_type | Focus | Instructions | |-------|---------------|-------|--------------| | test-explorer | Explore | Test files for usage patterns | Search `.claude/react/packages/*/src/__tests__/` for test files mentioning the topic. Extract actual usage examples WITH file paths and line numbers. | | source-explorer | Explore | Warnings/errors in source | Search `.claude/react/packages/*/src/` for console.error, console.warn, and error messages mentioning the topic. Document trigger conditions. | | git-historian | Explore | Commit messages | Run `git log --all --grep="" --oneline -50` in `.claude/react`. Read full commit messages for context. | | pr-researcher | Explore | PRs introducing/modifying API | Run `gh pr list -R facebook/react --search "" --state all --limit 20`. Read key PR descriptions and comments. | | issue-hunter | Explore | Issues showing confusion | Search issues in both `facebook/react` and `reactjs/react.dev` repos. Look for common questions and misunderstandings. | | types-inspector | Explore | Flow + TypeScript signatures | Find Flow types in `.claude/react/packages/*/src/*.js` (look for `@flow` annotations). Find TS types in `.claude/react/packages/*/index.d.ts`. Note discrepancies. | ### Step 3: Agent Prompts Use these exact prompts when spawning agents: #### test-explorer ``` You are researching React's . CRITICAL: Do NOT rely on your prior knowledge about this API. Your training may contain outdated or incorrect patterns. Only report what you find in the source files. Your task: Find test files in .claude/react that demonstrate usage. 1. Search for test files: Glob for `**/__tests__/**/**` and `**/__tests__/**/*.js` then grep for 2. For each relevant test file, extract: - The test description (describe/it blocks) - The actual usage code - Any assertions about behavior - Edge cases being tested 3. Report findings with exact file paths and line numbers Format your output as: ## Test File: ### Test: "" ```javascript ``` **Behavior:** ``` #### source-explorer ``` You are researching React's . CRITICAL: Do NOT rely on your prior knowledge about this API. Only report what you find in the source files. Your task: Find warnings, errors, and implementation details for . 1. Search .claude/react/packages/*/src/ for: - console.error mentions of - console.warn mentions of - Error messages mentioning - The main implementation file 2. For each warning/error, document: - The exact message text - The condition that triggers it - The source file and line number Format your output as: ## Warnings & Errors | Message | Trigger Condition | Source | |---------|------------------|--------| | "" | | | ## Implementation Notes ``` #### git-historian ``` You are researching React's . CRITICAL: Do NOT rely on your prior knowledge. Only report what you find in git history. Your task: Find commit messages that explain design decisions. 1. Run: cd .claude/react && git log --all --grep="" --oneline -50 2. For significant commits, read full message: git show --stat 3. Look for: - Initial introduction of the API - Bug fixes (reveal edge cases) - Behavior changes - Deprecation notices Format your output as: ## Key Commits ### - **Date:** **Context:** **Impact:** ``` #### pr-researcher ``` You are researching React's . CRITICAL: Do NOT rely on your prior knowledge. Only report what you find in PRs. Your task: Find PRs that introduced or modified . 1. Run: gh pr list -R facebook/react --search "" --state all --limit 20 --json number,title,url 2. For promising PRs, read details: gh pr view -R facebook/react 3. Look for: - The original RFC/motivation - Design discussions in comments - Alternative approaches considered - Breaking changes Format your output as: ## Key PRs ### PR #: **URL:** <url> **Summary:** <what it introduced/changed> **Design Rationale:** <why this approach> **Discussion Highlights:** <key points from comments> ``` #### issue-hunter ``` You are researching React's <TOPIC>. CRITICAL: Do NOT rely on your prior knowledge. Only report what you find in issues. Your task: Find issues that reveal common confusion about <TOPIC>. 1. Search facebook/react: gh issue list -R facebook/react --search "<topic>" --state all --limit 20 --json number,title,url 2. Search reactjs/react.dev: gh issue list -R reactjs/react.dev --search "<topic>" --state all --limit 20 --json number,title,url 3. For each issue, identify: - What the user was confused about - What the resolution was - Any gotchas revealed Format your output as: ## Common Confusion ### Issue #<number>: <title> **Repo:** <facebook/react or reactjs/react.dev> **Confusion:** <what they misunderstood> **Resolution:** <correct understanding> **Gotcha:** <if applicable> ``` #### types-inspector ``` You are researching React's <TOPIC>. CRITICAL: Do NOT rely on your prior knowledge. Only report what you find in type definitions. Your task: Find and compare Flow and TypeScript type signatures for <TOPIC>. 1. Flow types (source of truth): Search .claude/react/packages/*/src/*.js for @flow annotations related to <topic> 2. TypeScript types: Search .claude/react/packages/*/index.d.ts and @types/react 3. Compare and note any discrepancies Format your output as: ## Flow Types (Source of Truth) **File:** <path> ```flow <exact type definition> ``` ## TypeScript Types **File:** <path> ```typescript <exact type definition> ``` ## Discrepancies <any differences between Flow and TS definitions> ``` ### Step 4: Synthesize Results After all agents complete, combine their findings into a single research document. **DO NOT add information from your own knowledge.** Only include what agents found in sources. ### Step 5: Save Output Write the final document to `.claude/research/<topic>.md` Replace spaces in topic with hyphens (e.g., "suspense boundaries" → "suspense-boundaries.md") ## Output Document Template ```markdown # React Research: <topic> > Generated by /react-expert on YYYY-MM-DD > Sources: React repo (commit <hash>), N PRs, M issues ## Summary [Brief summary based SOLELY on source findings, not prior knowledge] ## API Signature ### Flow Types (Source of Truth) [From types-inspector agent] ### TypeScript Types [From types-inspector agent] ### Discrepancies [Any differences between Flow and TS] ## Usage Examples ### From Tests [From test-explorer agent - with file:line references] ### From PRs/Issues [Real-world patterns from discussions] ## Caveats & Gotchas [Each with source link] - **<gotcha>** - Source: <link> ## Warnings & Errors | Message | Trigger Condition | Source File | |---------|------------------|-------------| [From source-explorer agent] ## Common Confusion [From issue-hunter agent] ## Design Decisions [From git-historian and pr-researcher agents] ## Source Links ### Commits - <hash>: <description> ### Pull Requests - PR #<number>: <title> - <url> ### Issues - Issue #<number>: <title> - <url> ``` ## Common Mistakes to Avoid 1. **Trusting prior knowledge** - If you "know" something about the API, find the source evidence anyway 2. **Generating example code** - Every code example must come from an actual source file 3. **Skipping agents** - All 6 agents must run; each provides unique perspective 4. **Summarizing without sources** - Every claim needs a file:line or PR/issue reference 5. **Using web search** - No Stack Overflow, no blog posts, no social media ## Verification Checklist Before finalizing the research document: - [ ] React repo is at `.claude/react` with known commit hash - [ ] All 6 agents were spawned in parallel - [ ] Every code example has a source file reference - [ ] Warnings/errors table has source locations - [ ] No claims made without source evidence - [ ] Discrepancies between Flow/TS types documented - [ ] Source links section is complete ================================================ FILE: .claude/skills/review-docs/SKILL.md ================================================ --- name: review-docs description: Use when reviewing React documentation for structure, components, and style compliance --- CRITICAL: do not load these skills yourself. Run these tasks in parallel for the given file(s). Each agent checks different aspects—not all apply to every file: - [ ] Ask docs-reviewer agent to review {files} with docs-writer-learn (only for files in src/content/learn/). - [ ] Ask docs-reviewer agent to review {files} with docs-writer-reference (only for files in src/content/reference/). - [ ] Ask docs-reviewer agent to review {files} with docs-writer-blog (only for files in src/content/blog/). - [ ] Ask docs-reviewer agent to review {files} with docs-voice (all documentation files). - [ ] Ask docs-reviewer agent to review {files} with docs-components (all documentation files). - [ ] Ask docs-reviewer agent to review {files} with docs-sandpack (files containing Sandpack examples). If no file is specified, check git status for modified MDX files in `src/content/`. The docs-reviewer will return a checklist of the issues it found. Respond with the full checklist and line numbers from all agents, and prompt the user to create a plan to fix these issues. ================================================ FILE: .eslintignore ================================================ scripts plugins next.config.js .claude/ ================================================ FILE: .eslintrc ================================================ { "root": true, "extends": "next/core-web-vitals", "parser": "@typescript-eslint/parser", "plugins": ["@typescript-eslint", "eslint-plugin-react-compiler", "local-rules"], "rules": { "no-unused-vars": "off", "@typescript-eslint/no-unused-vars": ["error", {"varsIgnorePattern": "^_"}], "react-hooks/exhaustive-deps": "error", "react/no-unknown-property": ["error", {"ignore": ["meta"]}], "react-compiler/react-compiler": "error", "local-rules/lint-markdown-code-blocks": "error" }, "env": { "node": true, "commonjs": true, "browser": true, "es6": true }, "overrides": [ { "files": ["src/content/**/*.md"], "parser": "./eslint-local-rules/parser", "parserOptions": { "sourceType": "module" }, "rules": { "no-unused-vars": "off", "@typescript-eslint/no-unused-vars": "off", "react-hooks/exhaustive-deps": "off", "react/no-unknown-property": "off", "react-compiler/react-compiler": "off", "local-rules/lint-markdown-code-blocks": "error" } } ] } ================================================ FILE: .github/ISSUE_TEMPLATE/0-bug.yml ================================================ name: "🐛 Report a bug" description: "Report a problem on the website." title: "[Bug]: " labels: ["bug: unconfirmed"] body: - type: textarea attributes: label: Summary description: | A clear and concise summary of what the bug is. placeholder: | Example bug report: When I click the "Submit" button on "Feedback", nothing happens. validations: required: true - type: input attributes: label: Page description: | What page(s) did you encounter this bug on? placeholder: | https://react.dev/ validations: required: true - type: textarea attributes: label: Details description: | Please provide any additional details about the bug. placeholder: | Example details: The "Submit" button is unresponsive. I've tried refreshing the page and using a different browser, but the issue persists. validations: required: false ================================================ FILE: .github/ISSUE_TEMPLATE/1-typo.yml ================================================ name: "🤦 Typo or mistake" description: "Report a typo or mistake in the docs." title: "[Typo]: " labels: ["type: typos"] body: - type: textarea attributes: label: Summary description: | A clear and concise summary of what the mistake is. placeholder: | Example: The code example on the "useReducer" page includes an unused variable `nextId`. validations: required: true - type: input attributes: label: Page description: | What page is the typo on? placeholder: | https://react.dev/ validations: required: true - type: textarea attributes: label: Details description: | Please provide a explanation for why this is a mistake. placeholder: | Example mistake: In the "useReducer" section of the "API Reference" page, the code example under "Writing a reducer function" includes an unused variable `nextId` that should be removed. validations: required: false ================================================ FILE: .github/ISSUE_TEMPLATE/2-suggestion.yml ================================================ name: "💡 Suggestions" description: "Suggest a new page, section, or edit for an existing page." title: "[Suggestion]: " labels: ["type: documentation"] body: - type: textarea attributes: label: Summary description: | A clear and concise summary of what we should add. placeholder: | Example: Add a new page for how to use React with TypeScript. validations: required: true - type: input attributes: label: Page description: | What page is this about? placeholder: | https://react.dev/ validations: required: false - type: textarea attributes: label: Details description: | Please provide a explanation for what you're suggesting. placeholder: | Example: I think it would be helpful to have a page that explains how to use React with TypeScript. This could include a basic example of a component written in TypeScript, and a link to the TypeScript documentation. validations: required: true ================================================ FILE: .github/ISSUE_TEMPLATE/3-framework.yml ================================================ name: "📄 Suggest new framework" description: "I am a framework author applying to be included as a recommended framework." title: "[Framework]: " labels: ["type: framework"] body: - type: markdown attributes: value: | ## Apply to be included as a recommended React framework _This form is for framework authors to apply to be included as a recommended [React framework](https://react.dev/learn/creating-a-react-app). If you are not a framework author, please contact the authors before submitting._ Our goal when recommending a framework is to start developers with a React project that solves common problems like code splitting, data fetching, routing, and HTML generation without any extra work later. We believe this will allow users to get started quickly with React, and scale their app to production. While we understand that many frameworks may want to be featured, this page is not a place to advertise every possible React framework or all frameworks that you can add React to. There are many great frameworks that offer support for React that are not listed in our guides. The frameworks we recommend have invested significantly in the React ecosystem, and collaborated with the React team to be compatible with our [full-stack React architecture vision](https://react.dev/learn/creating-a-react-app#which-features-make-up-the-react-teams-full-stack-architecture-vision). To be included, frameworks must meet the following criteria: - **Free & open-source**: must be open source and free to use. - **Well maintained**. must be actively maintained, providing bug fixes and improvements. - **Active community**: must have a sufficiently large and active community to support users. - **Clear onboarding**: must have clear install steps to install the React version of the framework. - **Ecosystem compatibility**: must support using the full range of libraries and tools in the React ecosystem. - **Self-hosting option**: must support an option to self-host applications without losing access to features. - **Developer experience**. must allow developers to be productive by supporting features like Fast Refresh. - **User experience**. must provide built-in support for common problems like routing and data-fetching. - **Compatible with our future vision for React**. React evolves over time, and frameworks that do not align with React’s direction risk isolating their users from the main React ecosystem over time. To be included on this page we must feel confident that the framework is setting its users up for success with React over time. Please note, we have reviewed most of the popular frameworks available today, so it is unlikely we have not considered your framework already. But if you think we missed something, please complete the application below. - type: input attributes: label: Name description: | What is the name of your framework? validations: required: true - type: input attributes: label: Homepage description: | What is the URL of your homepage? validations: required: true - type: input attributes: label: Install instructions description: | What is the URL of your getting started guide? validations: required: true - type: dropdown attributes: label: Is your framework open source? description: | We only recommend free and open source frameworks. options: - 'No' - 'Yes' validations: required: true - type: textarea attributes: label: Well maintained description: | Please describe how your framework is actively maintained. Include recent releases, bug fixes, and improvements as examples. validations: required: true - type: textarea attributes: label: Active community description: | Please describe your community. Include the size of your community, and links to community resources. validations: required: true - type: textarea attributes: label: Clear onboarding description: | Please describe how a user can install your framework with React. Include links to any relevant documentation. validations: required: true - type: textarea attributes: label: Ecosystem compatibility description: | Please describe any limitations your framework has with the React ecosystem. Include any libraries or tools that are not compatible with your framework. validations: required: true - type: textarea attributes: label: Self-hosting option description: | Please describe how your framework supports self-hosting. Include any limitations to features when self-hosting. Also include whether you require a server to deploy your framework. validations: required: true - type: textarea attributes: label: Developer Experience description: | Please describe how your framework provides a great developer experience. Include any limitations to React features like React DevTools, Chrome DevTools, and Fast Refresh. validations: required: true - type: textarea attributes: label: User Experience description: | Please describe how your framework helps developers create high quality user experiences by solving common use-cases. Include specifics for how your framework offers built-in support for code-splitting, routing, HTML generation, and data-fetching in a way that avoids client/server waterfalls by default. Include details on how you offer features such as SSG and SSR. validations: required: true - type: textarea attributes: label: Compatible with our future vision for React description: | Please describe how your framework aligns with our future vision for React. Include how your framework will evolve with React over time, and your plans to support future React features like React Server Components. validations: required: true ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ contact_links: - name: 📃 Bugs in React url: https://github.com/facebook/react/issues/new/choose about: This issue tracker is not for bugs in React. Please file React issues here. - name: 🤔 Questions and Help url: https://reactjs.org/community/support.html about: This issue tracker is not for support questions. Please refer to the React community's help and discussion forums. ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ <!-- 日本語版 (ja.react.dev) リポジトリでのPR/Issueは、日本語版固有の問題 (翻訳や日本語版独自機能に関係するもの)のみ受け付けます。 全言語に関わる問題や改善(英語部分のスペルミス、コードサンプルの修正、ビルドシステム改善等) については、英語版リポジトリ (https://github.com/reactjs/react.dev) でPR/Issueを作成してください。 日本語版の作業フローについては以下を参照してください。 https://github.com/reactjs/ja.react.dev/wiki --> ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: "npm" directory: "/" schedule: interval: "weekly" # Disable Dependabot. Doing it here so it propagates to translation forks. open-pull-requests-limit: 0 ================================================ FILE: .github/workflows/analyze.yml ================================================ name: Analyze Bundle on: pull_request: push: branches: - main # change this if your default branch is named differently workflow_dispatch: permissions: {} jobs: analyze: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up node uses: actions/setup-node@v4 with: node-version: '20.x' cache: yarn cache-dependency-path: yarn.lock - name: Restore cached node_modules uses: actions/cache@v4 with: path: '**/node_modules' key: node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }} - name: Install deps run: yarn install --frozen-lockfile - name: Restore next build uses: actions/cache@v4 id: restore-build-cache env: cache-name: cache-next-build with: path: .next/cache # change this if you prefer a more strict cache key: ${{ runner.os }}-build-${{ env.cache-name }} - name: Build next.js app # change this if your site requires a custom build command run: ./node_modules/.bin/next build # Here's the first place where next-bundle-analysis' own script is used # This step pulls the raw bundle stats for the current bundle - name: Analyze bundle run: npx -p nextjs-bundle-analysis@0.5.0 report - name: Upload bundle uses: actions/upload-artifact@v4 with: path: .next/analyze/__bundle_analysis.json name: bundle_analysis.json - name: Download base branch bundle stats uses: dawidd6/action-download-artifact@268677152d06ba59fcec7a7f0b5d961b6ccd7e1e if: success() && github.event.number with: workflow: analyze.yml branch: ${{ github.event.pull_request.base.ref }} name: bundle_analysis.json path: .next/analyze/base/bundle # And here's the second place - this runs after we have both the current and # base branch bundle stats, and will compare them to determine what changed. # There are two configurable arguments that come from package.json: # # - budget: optional, set a budget (bytes) against which size changes are measured # it's set to 350kb here by default, as informed by the following piece: # https://infrequently.org/2021/03/the-performance-inequality-gap/ # # - red-status-percentage: sets the percent size increase where you get a red # status indicator, defaults to 20% # # Either of these arguments can be changed or removed by editing the `nextBundleAnalysis` # entry in your package.json file. - name: Compare with base branch bundle if: success() && github.event.number run: ls -laR .next/analyze/base && npx -p nextjs-bundle-analysis compare - name: Upload analysis comment uses: actions/upload-artifact@v4 with: name: analysis_comment.txt path: .next/analyze/__bundle_analysis_comment.txt - name: Save PR number run: echo ${{ github.event.number }} > ./pr_number - name: Upload PR number uses: actions/upload-artifact@v4 with: name: pr_number path: ./pr_number # The actual commenting happens in the other action, matching the guidance in # https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ ================================================ FILE: .github/workflows/analyze_comment.yml ================================================ name: Analyze Bundle (Comment) on: workflow_run: workflows: ['Analyze Bundle'] types: - completed permissions: contents: read issues: write pull-requests: write jobs: comment: runs-on: ubuntu-latest if: > ${{ github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' }} steps: - name: Download base branch bundle stats uses: dawidd6/action-download-artifact@268677152d06ba59fcec7a7f0b5d961b6ccd7e1e with: workflow: analyze.yml run_id: ${{ github.event.workflow_run.id }} name: analysis_comment.txt path: analysis_comment.txt - name: Download PR number uses: dawidd6/action-download-artifact@268677152d06ba59fcec7a7f0b5d961b6ccd7e1e with: workflow: analyze.yml run_id: ${{ github.event.workflow_run.id }} name: pr_number path: pr_number - name: Get comment body id: get-comment-body if: success() run: | echo 'body<<EOF' >> $GITHUB_OUTPUT echo '' >> $GITHUB_OUTPUT echo '## Size changes' >> $GITHUB_OUTPUT echo '' >> $GITHUB_OUTPUT echo '<details>' >> $GITHUB_OUTPUT echo '' >> $GITHUB_OUTPUT cat analysis_comment.txt/__bundle_analysis_comment.txt >> $GITHUB_OUTPUT echo '' >> $GITHUB_OUTPUT echo '</details>' >> $GITHUB_OUTPUT echo '' >> $GITHUB_OUTPUT echo 'EOF' >> $GITHUB_OUTPUT pr_number=$(cat pr_number/pr_number) echo "pr-number=$pr_number" >> $GITHUB_OUTPUT - name: Comment uses: marocchino/sticky-pull-request-comment@52423e01640425a022ef5fd42c6fb5f633a02728 with: header: next-bundle-analysis number: ${{ steps.get-comment-body.outputs.pr-number }} message: ${{ steps.get-comment-body.outputs.body }} ================================================ FILE: .github/workflows/discord_notify.yml ================================================ name: Discord Notify on: pull_request_target: types: [opened, ready_for_review] permissions: {} jobs: check_maintainer: uses: facebook/react/.github/workflows/shared_check_maintainer.yml@main permissions: # Used by check_maintainer contents: read with: actor: ${{ github.event.pull_request.user.login }} notify: if: ${{ needs.check_maintainer.outputs.is_core_team == 'true' }} needs: check_maintainer runs-on: ubuntu-latest steps: - name: Discord Webhook Action uses: tsickert/discord-webhook@v6.0.0 with: webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }} embed-author-name: ${{ github.event.pull_request.user.login }} embed-author-url: ${{ github.event.pull_request.user.html_url }} embed-author-icon-url: ${{ github.event.pull_request.user.avatar_url }} embed-title: '#${{ github.event.number }} (+${{github.event.pull_request.additions}} -${{github.event.pull_request.deletions}}): ${{ github.event.pull_request.title }}' embed-description: ${{ github.event.pull_request.body }} embed-url: ${{ github.event.pull_request.html_url }} ================================================ FILE: .github/workflows/label_core_team_prs.yml ================================================ name: Label Core Team PRs on: pull_request_target: permissions: {} env: TZ: /usr/share/zoneinfo/America/Los_Angeles # https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout SEGMENT_DOWNLOAD_TIMEOUT_MINS: 1 jobs: check_maintainer: uses: facebook/react/.github/workflows/shared_check_maintainer.yml@main permissions: # Used by check_maintainer contents: read with: actor: ${{ github.event.pull_request.user.login }} label: if: ${{ needs.check_maintainer.outputs.is_core_team == 'true' }} runs-on: ubuntu-latest needs: check_maintainer permissions: # Used to add labels on issues issues: write # Used to add labels on PRs pull-requests: write steps: - name: Label PR as React Core Team uses: actions/github-script@v7 with: script: | github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: ${{ github.event.number }}, labels: ['React Core Team'] }); ================================================ FILE: .github/workflows/site_lint.yml ================================================ name: Site Lint / Heading ID check on: push: branches: - main # change this if your default branch is named differently pull_request: types: [opened, synchronize, reopened] permissions: {} jobs: lint: runs-on: ubuntu-latest name: Lint on node 20.x and ubuntu-latest steps: - uses: actions/checkout@v4 - name: Use Node.js 20.x uses: actions/setup-node@v4 with: node-version: 20.x cache: yarn cache-dependency-path: yarn.lock - name: Restore cached node_modules uses: actions/cache@v4 with: path: '**/node_modules' key: node_modules-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }} - name: Install deps run: yarn install --frozen-lockfile - name: Lint codebase run: yarn ci-check - name: Textlint (Japanese) run: yarn textlint ================================================ FILE: .gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies node_modules /.pnp .pnp.js # testing /coverage # next.js /.next/ /out/ # production /build # misc .DS_Store *.pem tsconfig.tsbuildinfo # debug npm-debug.log* yarn-debug.log* yarn-error.log* # local env files .env.local .env.development.local .env.test.local .env.production.local # vercel .vercel # external fonts public/fonts/**/Optimistic_*.woff2 # rss public/rss.xml # claude local settings .claude/*.local.* .claude/react/ ================================================ FILE: .husky/pre-commit ================================================ #!/bin/sh . "$(dirname "$0")/_/husky.sh" yarn lint-staged ================================================ FILE: .prettierignore ================================================ src/content/**/*.md ================================================ FILE: .prettierrc ================================================ { "bracketSpacing": false, "singleQuote": true, "bracketSameLine": true, "trailingComma": "es5", "printWidth": 80, "overrides": [ { "files": "*.css", "options": { "parser": "css" } }, { "files": "*.md", "options": { "parser": "mdx" } } ] } ================================================ FILE: CLAUDE.md ================================================ # CLAUDE.md This file provides guidance to Claude Code when working with this repository. ## Project Overview This is the React documentation website (react.dev), built with Next.js 15.1.11 and React 19. Documentation is written in MDX format. ## Development Commands ```bash yarn build # Production build yarn lint # Run ESLint yarn lint:fix # Auto-fix lint issues yarn tsc # TypeScript type checking yarn check-all # Run prettier, lint:fix, tsc, and rss together ``` ## Project Structure ``` src/ ├── content/ # Documentation content (MDX files) │ ├── learn/ # Tutorial/learning content │ ├── reference/ # API reference docs │ ├── blog/ # Blog posts │ └── community/ # Community pages ├── components/ # React components ├── pages/ # Next.js pages ├── hooks/ # Custom React hooks ├── utils/ # Utility functions └── styles/ # CSS/Tailwind styles ``` ## Code Conventions ### TypeScript/React - Functional components only - Tailwind CSS for styling ### Documentation Style When editing files in `src/content/`, the appropriate skill will be auto-suggested: - `src/content/learn/` - Learn page structure and tone - `src/content/reference/` - Reference page structure and tone For MDX components (DeepDive, Pitfall, Note, etc.), invoke `/docs-components`. For Sandpack code examples, invoke `/docs-sandpack`. See `.claude/docs/react-docs-patterns.md` for comprehensive style guidelines. Prettier is used for formatting (config in `.prettierrc`). ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies within all project spaces, and it also applies when an individual is representing the project or its community in public spaces. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at <opensource-conduct@fb.com>. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing Thank you for your interest in contributing to the React Docs! ## Code of Conduct Facebook has adopted a Code of Conduct that we expect project participants to adhere to. Please [read the full text](https://code.facebook.com/codeofconduct) so that you can understand what actions will and will not be tolerated. ## Technical Writing Tips This is a [good summary](https://medium.com/@kvosswinkel/coding-like-a-journalist-ee52360a16bc) for things to keep in mind when writing technical docs. ## Guidelines for Text **Different sections intentionally have different styles.** The documentation is divided into sections to cater to different learning styles and use cases. When editing an article, try to match the surrounding text in tone and style. When creating a new article, try to match the tone of the other articles in the same section. Learn about the motivation behind each section below. **[Learn React](https://react.dev/learn)** is designed to introduce fundamental concepts in a step-by-step way. Each individual article in Learn React builds on the knowledge from the previous ones, so make sure not to add any "cyclical dependencies" between them. It is important that the reader can start with the first article and work their way to the last Learn React article without ever having to "look ahead" for a definition. This explains some ordering choices (e.g. that state is explained before events, or that "thinking in React" doesn't use refs). Learn React also serves as a reference manual for React concepts, so it is important to be very strict about their definitions and relationships between them. **[API Reference](https://react.dev/reference/react)** is organized by APIs rather than concepts. It is intended to be exhaustive. Any corner cases or recommendations that were skipped for brevity in Learn React should be mentioned in the reference documentation for the corresponding APIs. **Try to follow your own instructions.** When writing step-by-step instructions (e.g. how to install something), try to forget everything you know about the topic, and actually follow the instructions you wrote, a single step at time. Often you will discover that there is implicit knowledge that you forgot to mention, or that there are missing or out-of-order steps in the instructions. Bonus points for getting *somebody else* to follow the steps and watching what they struggle with. Often it would be something very simple that you have not anticipated. ## Guidelines for Code Examples ### Syntax #### Prefer JSX to `createElement`. Ignore this if you're specifically describing `createElement`. #### Use `const` where possible, otherwise `let`. Don't use `var`. Ignore this if you're specifically writing about ES5. #### Don't use ES6 features when equivalent ES5 features have no downsides. Remember that ES6 is still new to a lot of people. While we use it in many places (`const` / `let`, classes, arrow functions), if the equivalent ES5 code is just as straightforward and readable, consider using it. In particular, you should prefer named `function` declarations over `const myFunction = () => ...` arrows for top-level functions. However, you *should* use arrow functions where they provide a tangible improvement (such as preserving `this` context inside a component). Consider both sides of the tradeoff when deciding whether to use a new feature. #### Don't use features that aren't standardized yet. For example, **don't** write this: ```js class MyComponent extends React.Component { state = {value: ''}; handleChange = (e) => { this.setState({value: e.target.value}); }; } ``` Instead, **do** write this: ```js class MyComponent extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); this.state = {value: ''}; } handleChange(e) { this.setState({value: e.target.value}); } } ``` Ignore this rule if you're specifically describing an experimental proposal. Make sure to mention its experimental nature in the code and in the surrounding text. ### Style - Use semicolons. - No space between function names and parens (`method() {}` not `method () {}`). - When in doubt, use the default style favored by [Prettier](https://prettier.io/playground/). - Always capitalize React concepts such as Hooks, Effects, and Transitions. ### Highlighting Use `js` as the highlighting language in Markdown code blocks: ```` ```js // code ``` ```` Sometimes you'll see blocks with numbers. They tell the website to highlight specific lines. You can highlight a single line: ```` ```js {2} function hello() { // this line will get highlighted } ``` ```` A range of lines: ```` ```js {2-4} function hello() { // these lines // will get // highlighted } ``` ```` Or even multiple ranges: ```` ```js {2-4,6} function hello() { // these lines // will get // highlighted console.log('hello'); // also this one console.log('there'); } ``` ```` Be mindful that if you move some code in an example with highlighting, you also need to update the highlighting. Don't be afraid to often use highlighting! It is very valuable when you need to focus the reader's attention on a particular detail that's easy to miss. ================================================ FILE: LICENSE-DOCS.md ================================================ Attribution 4.0 International ======================================================================= Creative Commons Corporation ("Creative Commons") is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an "as-is" basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible. Using Creative Commons Public Licenses Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses. Considerations for licensors: Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC- licensed material, or material used under an exception or limitation to copyright. More considerations for licensors: wiki.creativecommons.org/Considerations_for_licensors Considerations for the public: By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor's permission is not necessary for any reason--for example, because of any applicable exception or limitation to copyright--then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. More_considerations for the public: wiki.creativecommons.org/Considerations_for_licensees ======================================================================= Creative Commons Attribution 4.0 International Public License By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. Section 1 -- Definitions. a. Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. b. Adapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License. c. Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. d. Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. e. Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. f. Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License. g. Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. h. Licensor means the individual(s) or entity(ies) granting rights under this Public License. i. Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. j. Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. k. You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. Section 2 -- Scope. a. License grant. 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: a. reproduce and Share the Licensed Material, in whole or in part; and b. produce, reproduce, and Share Adapted Material. 2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. 3. Term. The term of this Public License is specified in Section 6(a). 4. Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a) (4) never produces Adapted Material. 5. Downstream recipients. a. Offer from the Licensor -- Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. b. No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. 6. No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). b. Other rights. 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. 2. Patent and trademark rights are not licensed under this Public License. 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties. Section 3 -- License Conditions. Your exercise of the Licensed Rights is expressly made subject to the following conditions. a. Attribution. 1. If You Share the Licensed Material (including in modified form), You must: a. retain the following if it is supplied by the Licensor with the Licensed Material: i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); ii. a copyright notice; iii. a notice that refers to this Public License; iv. a notice that refers to the disclaimer of warranties; v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; b. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and c. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. 4. If You Share Adapted Material You produce, the Adapter's License You apply must not prevent recipients of the Adapted Material from complying with this Public License. Section 4 -- Sui Generis Database Rights. Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database; b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material; and c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. Section 5 -- Disclaimer of Warranties and Limitation of Liability. a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. Section 6 -- Term and Termination. a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or 2. upon express reinstatement by the Licensor. For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. c. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. d. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. Section 7 -- Other Terms and Conditions. a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. Section 8 -- Interpretation. a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. ======================================================================= Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the "Licensor." Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at creativecommons.org/policies, Creative Commons does not authorize the use of the trademark "Creative Commons" or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses. Creative Commons may be contacted at creativecommons.org. ================================================ FILE: README.md ================================================ # react.dev This repo contains the source code and documentation powering [react.dev](https://react.dev/). > 以下はオリジナル(英語版)リポジトリの README です。日本語版 React ドキュメントへの貢献(翻訳・修正など)に興味がある方は[こちらの Wiki ページ](https://github.com/reactjs/ja.react.dev/wiki)を参照してください。 ## Getting started ### Prerequisites 1. Git 1. Node: any version starting with v16.8.0 or greater 1. Yarn: See [Yarn website for installation instructions](https://yarnpkg.com/lang/en/docs/install/) 1. A fork of the repo (for any contributions) 1. A clone of the [react.dev repo](https://github.com/reactjs/react.dev) on your local machine ### Installation 1. `cd react.dev` to go into the project root 3. `yarn` to install the website's npm dependencies ### Running locally 1. `yarn dev` to start the development server (powered by [Next.js](https://nextjs.org/)) 1. `open http://localhost:3000` to open the site in your favorite browser ## Contributing ### Guidelines The documentation is divided into several sections with a different tone and purpose. If you plan to write more than a few sentences, you might find it helpful to get familiar with the [contributing guidelines](https://github.com/reactjs/react.dev/blob/main/CONTRIBUTING.md#guidelines-for-text) for the appropriate sections. ### Create a branch 1. `git checkout main` from any folder in your local `react.dev` repository 1. `git pull origin main` to ensure you have the latest main code 1. `git checkout -b the-name-of-my-branch` (replacing `the-name-of-my-branch` with a suitable name) to create a branch ### Make the change 1. Follow the ["Running locally"](#running-locally) instructions 1. Save the files and check in the browser 1. Changes to React components in `src` will hot-reload 1. Changes to markdown files in `content` will hot-reload 1. If working with plugins, you may need to remove the `.cache` directory and restart the server ### Test the change 1. If possible, test any visual changes in all latest versions of common browsers, on both desktop and mobile. 2. Run `yarn check-all`. (This will run Prettier, ESLint and validate types.) ### Push it 1. `git add -A && git commit -m "My message"` (replacing `My message` with a commit message, such as `Fix header logo on Android`) to stage and commit your changes 1. `git push my-fork-name the-name-of-my-branch` 1. Go to the [react.dev repo](https://github.com/reactjs/react.dev) and you should see recently pushed branches. 1. Follow GitHub's instructions. 1. If possible, include screenshots of visual changes. A preview build is triggered after your changes are pushed to GitHub. ## Translation If you are interested in translating `react.dev`, please see the current translation efforts [here](https://github.com/reactjs/react.dev/issues/4135). ## License Content submitted to [react.dev](https://react.dev/) is CC-BY-4.0 licensed, as found in the [LICENSE-DOCS.md](https://github.com/reactjs/react.dev/blob/main/LICENSE-DOCS.md) file. ================================================ FILE: colors.js ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ module.exports = { // Text colors primary: '#23272F', // gray-90 'primary-dark': '#F6F7F9', // gray-5 secondary: '#404756', // gray-70 'secondary-dark': '#EBECF0', // gray-10 tertiary: '#5E687E', // gray-50 'tertiary-dark': '#99A1B3', // gray-30 link: '#087EA4', // blue-50 'link-dark': '#58C4DC', // blue-40 syntax: '#EBECF0', // gray-10 wash: '#FFFFFF', 'wash-dark': '#23272F', // gray-90 card: '#F6F7F9', // gray-05 'card-dark': '#343A46', // gray-80 highlight: '#E6F7FF', // blue-10 'highlight-dark': 'rgba(88,175,223,.1)', border: '#EBECF0', // gray-10 'border-dark': '#343A46', // gray-80 'secondary-button': '#EBECF0', // gray-10 'secondary-button-dark': '#404756', // gray-70 brand: '#087EA4', // blue-40 'brand-dark': '#58C4DC', // blue-40 // Gray 'gray-95': '#16181D', 'gray-90': '#23272F', 'gray-80': '#343A46', 'gray-70': '#404756', 'gray-60': '#4E5769', 'gray-50': '#5E687E', 'gray-40': '#78839B', 'gray-30': '#99A1B3', 'gray-20': '#BCC1CD', 'gray-15': '#D0D3DC', 'gray-10': '#EBECF0', 'gray-5': '#F6F7F9', // Blue 'blue-80': '#043849', 'blue-60': '#045975', 'blue-50': '#087EA4', 'blue-40': '#149ECA', // Brand Blue 'blue-30': '#58C4DC', // unused 'blue-20': '#ABE2ED', 'blue-10': '#E6F7FF', // todo: doesn't match illustrations 'blue-5': '#E6F6FA', // Yellow 'yellow-60': '#B65700', 'yellow-50': '#C76A15', 'yellow-40': '#DB7D27', // unused 'yellow-30': '#FABD62', // unused 'yellow-20': '#FCDEB0', // unused 'yellow-10': '#FDE7C7', 'yellow-5': '#FEF5E7', // Purple 'purple-60': '#2B3491', // unused 'purple-50': '#575FB7', 'purple-40': '#6B75DB', 'purple-30': '#8891EC', 'purple-20': '#C3C8F5', // unused 'purple-10': '#E7E9FB', 'purple-5': '#F3F4FD', // Green 'green-60': '#2B6E62', 'green-50': '#388F7F', 'green-40': '#44AC99', 'green-30': '#7FCCBF', 'green-20': '#ABDED5', 'green-10': '#E5F5F2', 'green-5': '#F4FBF9', // RED 'red-60': '#712D28', 'red-50': '#A6423A', // unused 'red-40': '#C1554D', 'red-30': '#D07D77', 'red-20': '#E5B7B3', // unused 'red-10': '#F2DBD9', // unused 'red-5': '#FAF1F0', // MISC 'code-block': '#99a1b30f', // gray-30 @ 6% 'gradient-blue': '#58C4DC', // Only used for the landing gradient for now. github: { highlight: '#fffbdd', }, }; ================================================ FILE: eslint-local-rules/__tests__/fixtures/src/content/basic-error.md ================================================ ```jsx import {useState} from 'react'; function Counter() { const [count, setCount] = useState(0); setCount(count + 1); return <div>{count}</div>; } ``` ================================================ FILE: eslint-local-rules/__tests__/fixtures/src/content/duplicate-metadata.md ================================================ ```jsx title="Counter" {expectedErrors: {'react-compiler': [99]}} {expectedErrors: {'react-compiler': [2]}} import {useState} from 'react'; function Counter() { const [count, setCount] = useState(0); setCount(count + 1); return <div>{count}</div>; } ``` ================================================ FILE: eslint-local-rules/__tests__/fixtures/src/content/malformed-metadata.md ================================================ ```jsx {expectedErrors: {'react-compiler': 'invalid'}} import {useState} from 'react'; function Counter() { const [count, setCount] = useState(0); setCount(count + 1); return <div>{count}</div>; } ``` ================================================ FILE: eslint-local-rules/__tests__/fixtures/src/content/mixed-language.md ================================================ ```bash setCount() ``` ```txt import {useState} from 'react'; ``` ================================================ FILE: eslint-local-rules/__tests__/fixtures/src/content/stale-expected-error.md ================================================ ```jsx {expectedErrors: {'react-compiler': [3]}} function Hello() { return <h1>Hello</h1>; } ``` ================================================ FILE: eslint-local-rules/__tests__/fixtures/src/content/suppressed-error.md ================================================ ```jsx {expectedErrors: {'react-compiler': [4]}} import {useState} from 'react'; function Counter() { const [count, setCount] = useState(0); setCount(count + 1); return <div>{count}</div>; } ``` ================================================ FILE: eslint-local-rules/__tests__/lint-markdown-code-blocks.test.js ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ const assert = require('assert'); const fs = require('fs'); const path = require('path'); const {ESLint} = require('eslint'); const plugin = require('..'); const FIXTURES_DIR = path.join( __dirname, 'fixtures', 'src', 'content' ); const PARSER_PATH = path.join(__dirname, '..', 'parser.js'); function createESLint({fix = false} = {}) { return new ESLint({ useEslintrc: false, fix, plugins: { 'local-rules': plugin, }, overrideConfig: { parser: PARSER_PATH, plugins: ['local-rules'], rules: { 'local-rules/lint-markdown-code-blocks': 'error', }, parserOptions: { sourceType: 'module', }, }, }); } function readFixture(name) { return fs.readFileSync(path.join(FIXTURES_DIR, name), 'utf8'); } async function lintFixture(name, {fix = false} = {}) { const eslint = createESLint({fix}); const filePath = path.join(FIXTURES_DIR, name); const markdown = readFixture(name); const [result] = await eslint.lintText(markdown, {filePath}); return result; } async function run() { const basicResult = await lintFixture('basic-error.md'); assert.strictEqual( basicResult.messages.length, 1, 'expected one diagnostic' ); assert( basicResult.messages[0].message.includes('Calling setState during render'), 'expected message to mention setState during render' ); const suppressedResult = await lintFixture('suppressed-error.md'); assert.strictEqual( suppressedResult.messages.length, 0, 'expected suppression metadata to silence diagnostic' ); const staleResult = await lintFixture('stale-expected-error.md'); assert.strictEqual( staleResult.messages.length, 1, 'expected stale metadata error' ); assert.strictEqual( staleResult.messages[0].message, 'React Compiler expected error on line 3 was not triggered' ); const duplicateResult = await lintFixture('duplicate-metadata.md'); assert.strictEqual( duplicateResult.messages.length, 2, 'expected duplicate metadata to surface compiler diagnostic and stale metadata notice' ); const duplicateFixed = await lintFixture('duplicate-metadata.md', { fix: true, }); assert( duplicateFixed.output.includes( "{expectedErrors: {'react-compiler': [4]}}" ), 'expected duplicates to be rewritten to a single canonical block' ); assert( !duplicateFixed.output.includes('[99]'), 'expected stale line numbers to be removed from metadata' ); const mixedLanguageResult = await lintFixture('mixed-language.md'); assert.strictEqual( mixedLanguageResult.messages.length, 0, 'expected non-js code fences to be ignored' ); const malformedResult = await lintFixture('malformed-metadata.md'); assert.strictEqual( malformedResult.messages.length, 1, 'expected malformed metadata to fall back to compiler diagnostics' ); const malformedFixed = await lintFixture('malformed-metadata.md', { fix: true, }); assert( malformedFixed.output.includes( "{expectedErrors: {'react-compiler': [4]}}" ), 'expected malformed metadata to be replaced with canonical form' ); } run().catch(error => { console.error(error); process.exitCode = 1; }); ================================================ FILE: eslint-local-rules/index.js ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ const lintMarkdownCodeBlocks = require('./rules/lint-markdown-code-blocks'); module.exports = { rules: { 'lint-markdown-code-blocks': lintMarkdownCodeBlocks, }, }; ================================================ FILE: eslint-local-rules/package.json ================================================ { "name": "eslint-plugin-local-rules", "version": "0.0.0", "main": "index.js", "private": "true", "scripts": { "test": "node __tests__/lint-markdown-code-blocks.test.js" }, "devDependencies": { "eslint-mdx": "^2" } } ================================================ FILE: eslint-local-rules/parser.js ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ module.exports = require('eslint-mdx'); ================================================ FILE: eslint-local-rules/rules/diagnostics.js ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ function getRelativeLine(loc) { return loc?.start?.line ?? loc?.line ?? 1; } function getRelativeColumn(loc) { return loc?.start?.column ?? loc?.column ?? 0; } function getRelativeEndLine(loc, fallbackLine) { if (loc?.end?.line != null) { return loc.end.line; } if (loc?.line != null) { return loc.line; } return fallbackLine; } function getRelativeEndColumn(loc, fallbackColumn) { if (loc?.end?.column != null) { return loc.end.column; } if (loc?.column != null) { return loc.column; } return fallbackColumn; } /** * @param {import('./markdown').MarkdownCodeBlock} block * @param {Array<{detail: any, loc: any, message: string}>} diagnostics * @returns {Array<{detail: any, message: string, relativeStartLine: number, markdownLoc: {start: {line: number, column: number}, end: {line: number, column: number}}}>} */ function normalizeDiagnostics(block, diagnostics) { return diagnostics.map(({detail, loc, message}) => { const relativeStartLine = Math.max(getRelativeLine(loc), 1); const relativeStartColumn = Math.max(getRelativeColumn(loc), 0); const relativeEndLine = Math.max( getRelativeEndLine(loc, relativeStartLine), relativeStartLine ); const relativeEndColumn = Math.max( getRelativeEndColumn(loc, relativeStartColumn), relativeStartColumn ); const markdownStartLine = block.codeStartLine + relativeStartLine - 1; const markdownEndLine = block.codeStartLine + relativeEndLine - 1; return { detail, message, relativeStartLine, markdownLoc: { start: { line: markdownStartLine, column: relativeStartColumn, }, end: { line: markdownEndLine, column: relativeEndColumn, }, }, }; }); } module.exports = { normalizeDiagnostics, }; ================================================ FILE: eslint-local-rules/rules/lint-markdown-code-blocks.js ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ const { buildFenceLine, getCompilerExpectedLines, getSortedUniqueNumbers, hasCompilerEntry, metadataEquals, metadataHasExpectedErrorsToken, removeCompilerExpectedLines, setCompilerExpectedLines, } = require('./metadata'); const {normalizeDiagnostics} = require('./diagnostics'); const {parseMarkdownFile} = require('./markdown'); const {runReactCompiler} = require('./react-compiler'); module.exports = { meta: { type: 'problem', docs: { description: 'Run React Compiler on markdown code blocks', category: 'Possible Errors', }, fixable: 'code', hasSuggestions: true, schema: [], }, create(context) { return { Program(node) { const filename = context.getFilename(); if (!filename.endsWith('.md') || !filename.includes('src/content')) { return; } const sourceCode = context.getSourceCode(); const {blocks} = parseMarkdownFile(sourceCode.text, filename); // For each supported code block, run the compiler and reconcile metadata. for (const block of blocks) { const compilerResult = runReactCompiler( block.code, `${filename}#codeblock` ); const expectedLines = getCompilerExpectedLines(block.metadata); const expectedLineSet = new Set(expectedLines); const diagnostics = normalizeDiagnostics( block, compilerResult.diagnostics ); const errorLines = new Set(); const unexpectedDiagnostics = []; for (const diagnostic of diagnostics) { const line = diagnostic.relativeStartLine; errorLines.add(line); if (!expectedLineSet.has(line)) { unexpectedDiagnostics.push(diagnostic); } } const normalizedErrorLines = getSortedUniqueNumbers( Array.from(errorLines) ); const missingExpectedLines = expectedLines.filter( (line) => !errorLines.has(line) ); const desiredMetadata = normalizedErrorLines.length ? setCompilerExpectedLines(block.metadata, normalizedErrorLines) : removeCompilerExpectedLines(block.metadata); // Compute canonical metadata and attach an autofix when it differs. const metadataChanged = !metadataEquals( block.metadata, desiredMetadata ); const replacementLine = buildFenceLine(block.lang, desiredMetadata); const replacementDiffers = block.fence.rawText !== replacementLine; const applyReplacementFix = replacementDiffers ? (fixer) => fixer.replaceTextRange(block.fence.range, replacementLine) : null; const hasDuplicateMetadata = block.metadata.hadDuplicateExpectedErrors; const hasExpectedErrorsMetadata = metadataHasExpectedErrorsToken( block.metadata ); const shouldFixUnexpected = Boolean(applyReplacementFix) && normalizedErrorLines.length > 0 && (metadataChanged || hasDuplicateMetadata || !hasExpectedErrorsMetadata); let fixAlreadyAttached = false; for (const diagnostic of unexpectedDiagnostics) { const reportData = { node, loc: diagnostic.markdownLoc, message: diagnostic.message, }; if ( shouldFixUnexpected && applyReplacementFix && !fixAlreadyAttached ) { reportData.fix = applyReplacementFix; reportData.suggest = [ { desc: 'Add expectedErrors metadata to suppress these errors', fix: applyReplacementFix, }, ]; fixAlreadyAttached = true; } context.report(reportData); } // Assert that expectedErrors is actually needed if ( Boolean(applyReplacementFix) && missingExpectedLines.length > 0 && hasCompilerEntry(block.metadata) ) { const plural = missingExpectedLines.length > 1; const message = plural ? `React Compiler expected errors on lines ${missingExpectedLines.join( ', ' )} were not triggered` : `React Compiler expected error on line ${missingExpectedLines[0]} was not triggered`; const reportData = { node, loc: { start: { line: block.position.start.line, column: 0, }, end: { line: block.position.start.line, column: block.fence.rawText.length, }, }, message, }; if (!fixAlreadyAttached && applyReplacementFix) { reportData.fix = applyReplacementFix; fixAlreadyAttached = true; } else if (applyReplacementFix) { reportData.suggest = [ { desc: 'Remove stale expectedErrors metadata', fix: applyReplacementFix, }, ]; } context.report(reportData); } } }, }; }, }; ================================================ FILE: eslint-local-rules/rules/markdown.js ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ const remark = require('remark'); const {parseFenceMetadata} = require('./metadata'); /** * @typedef {Object} MarkdownCodeBlock * @property {string} code * @property {number} codeStartLine * @property {{start: {line: number, column: number}, end: {line: number, column: number}}} position * @property {{lineIndex: number, rawText: string, metaText: string, range: [number, number]}} fence * @property {string} filePath * @property {string} lang * @property {import('./metadata').FenceMetadata} metadata */ const SUPPORTED_LANGUAGES = new Set([ 'js', 'jsx', 'javascript', 'ts', 'tsx', 'typescript', ]); function computeLineOffsets(lines) { const offsets = []; let currentOffset = 0; for (const line of lines) { offsets.push(currentOffset); currentOffset += line.length + 1; } return offsets; } function parseMarkdownFile(content, filePath) { const tree = remark().parse(content); const lines = content.split('\n'); const lineOffsets = computeLineOffsets(lines); const blocks = []; function traverse(node) { if (!node || typeof node !== 'object') { return; } if (node.type === 'code') { const rawLang = node.lang || ''; const normalizedLang = rawLang.toLowerCase(); if (!normalizedLang || !SUPPORTED_LANGUAGES.has(normalizedLang)) { return; } const fenceLineIndex = (node.position?.start?.line ?? 1) - 1; const fenceStartOffset = node.position?.start?.offset ?? 0; const fenceLine = lines[fenceLineIndex] ?? ''; const fenceEndOffset = fenceStartOffset + fenceLine.length; let metaText = ''; if (fenceLine) { const prefixMatch = fenceLine.match(/^`{3,}\s*/); const prefixLength = prefixMatch ? prefixMatch[0].length : 3; metaText = fenceLine.slice(prefixLength + rawLang.length); } else if (node.meta) { metaText = ` ${node.meta}`; } const metadata = parseFenceMetadata(metaText); blocks.push({ lang: rawLang || normalizedLang, metadata, filePath, code: node.value || '', codeStartLine: (node.position?.start?.line ?? 1) + 1, position: { start: { line: fenceLineIndex + 1, column: (node.position?.start?.column ?? 1) - 1, }, end: { line: fenceLineIndex + 1, column: (node.position?.start?.column ?? 1) - 1 + fenceLine.length, }, }, fence: { lineIndex: fenceLineIndex, rawText: fenceLine, metaText, range: [fenceStartOffset, fenceEndOffset], }, }); return; } if ('children' in node && Array.isArray(node.children)) { for (const child of node.children) { traverse(child); } } } traverse(tree); return { content, blocks, lines, lineOffsets, }; } module.exports = { SUPPORTED_LANGUAGES, computeLineOffsets, parseMarkdownFile, }; ================================================ FILE: eslint-local-rules/rules/metadata.js ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /** * @typedef {{type: 'text', raw: string}} TextToken * @typedef {{ * type: 'expectedErrors', * entries: Record<string, Array<number>>, * raw?: string, * }} ExpectedErrorsToken * @typedef {TextToken | ExpectedErrorsToken} MetadataToken * * @typedef {{ * leading: string, * trailing: string, * tokens: Array<MetadataToken>, * parseError: boolean, * hadDuplicateExpectedErrors: boolean, * }} FenceMetadata */ const EXPECTED_ERRORS_BLOCK_REGEX = /\{\s*expectedErrors\s*:/; const REACT_COMPILER_KEY = 'react-compiler'; function getSortedUniqueNumbers(values) { return Array.from(new Set(values)) .filter((value) => typeof value === 'number' && !Number.isNaN(value)) .sort((a, b) => a - b); } function tokenizeMeta(body) { if (!body) { return []; } const tokens = []; let current = ''; let depth = 0; for (let i = 0; i < body.length; i++) { const char = body[i]; if (char === '{') { depth++; } else if (char === '}') { depth = Math.max(depth - 1, 0); } if (char === ' ' && depth === 0) { if (current) { tokens.push(current); current = ''; } continue; } current += char; } if (current) { tokens.push(current); } return tokens; } function normalizeEntryValues(values) { if (!Array.isArray(values)) { return []; } return getSortedUniqueNumbers(values); } function parseExpectedErrorsEntries(rawEntries) { const normalized = rawEntries .replace(/([{,]\s*)([a-zA-Z_$][\w$]*)\s*:/g, '$1"$2":') .replace(/'([^']*)'/g, '"$1"'); const parsed = JSON.parse(normalized); const entries = {}; if (parsed && typeof parsed === 'object') { for (const [key, value] of Object.entries(parsed)) { entries[key] = normalizeEntryValues(Array.isArray(value) ? value.flat() : value); } } return entries; } function parseExpectedErrorsToken(tokenText) { const match = tokenText.match(/^\{\s*expectedErrors\s*:\s*(\{[\s\S]*\})\s*\}$/); if (!match) { return null; } const entriesSource = match[1]; let parseError = false; let entries; try { entries = parseExpectedErrorsEntries(entriesSource); } catch (error) { parseError = true; entries = {}; } return { token: { type: 'expectedErrors', entries, raw: tokenText, }, parseError, }; } function parseFenceMetadata(metaText) { if (!metaText) { return { leading: '', trailing: '', tokens: [], parseError: false, hadDuplicateExpectedErrors: false, }; } const leading = metaText.match(/^\s*/)?.[0] ?? ''; const trailing = metaText.match(/\s*$/)?.[0] ?? ''; const bodyStart = leading.length; const bodyEnd = metaText.length - trailing.length; const body = metaText.slice(bodyStart, bodyEnd).trim(); if (!body) { return { leading, trailing, tokens: [], parseError: false, hadDuplicateExpectedErrors: false, }; } const tokens = []; let parseError = false; let sawExpectedErrors = false; let hadDuplicateExpectedErrors = false; for (const rawToken of tokenizeMeta(body)) { const normalizedToken = rawToken.trim(); if (!normalizedToken) { continue; } if (EXPECTED_ERRORS_BLOCK_REGEX.test(normalizedToken)) { const parsed = parseExpectedErrorsToken(normalizedToken); if (parsed) { if (sawExpectedErrors) { hadDuplicateExpectedErrors = true; // Drop duplicates. We'll rebuild the canonical block on write. continue; } tokens.push(parsed.token); parseError = parseError || parsed.parseError; sawExpectedErrors = true; continue; } } tokens.push({type: 'text', raw: normalizedToken}); } return { leading, trailing, tokens, parseError, hadDuplicateExpectedErrors, }; } function cloneMetadata(metadata) { return { leading: metadata.leading, trailing: metadata.trailing, parseError: metadata.parseError, hadDuplicateExpectedErrors: metadata.hadDuplicateExpectedErrors, tokens: metadata.tokens.map((token) => { if (token.type === 'expectedErrors') { const clonedEntries = {}; for (const [key, value] of Object.entries(token.entries)) { clonedEntries[key] = [...value]; } return {type: 'expectedErrors', entries: clonedEntries}; } return {type: 'text', raw: token.raw}; }), }; } function findExpectedErrorsToken(metadata) { return metadata.tokens.find((token) => token.type === 'expectedErrors') || null; } function getCompilerExpectedLines(metadata) { const token = findExpectedErrorsToken(metadata); if (!token) { return []; } return getSortedUniqueNumbers(token.entries[REACT_COMPILER_KEY] || []); } function hasCompilerEntry(metadata) { const token = findExpectedErrorsToken(metadata); return Boolean(token && token.entries[REACT_COMPILER_KEY]?.length); } function metadataHasExpectedErrorsToken(metadata) { return Boolean(findExpectedErrorsToken(metadata)); } function stringifyExpectedErrorsToken(token) { const entries = token.entries || {}; const keys = Object.keys(entries).filter((key) => entries[key].length > 0); if (keys.length === 0) { return ''; } keys.sort(); const segments = keys.map((key) => { const values = entries[key]; return `'${key}': [${values.join(', ')}]`; }); return `{expectedErrors: {${segments.join(', ')}}}`; } function stringifyFenceMetadata(metadata) { if (!metadata.tokens.length) { return ''; } const parts = metadata.tokens .map((token) => { if (token.type === 'expectedErrors') { return stringifyExpectedErrorsToken(token); } return token.raw; }) .filter(Boolean); if (!parts.length) { return ''; } const leading = metadata.leading || ' '; const trailing = metadata.trailing ? metadata.trailing.trimEnd() : ''; const body = parts.join(' '); return `${leading}${body}${trailing}`; } function buildFenceLine(lang, metadata) { const meta = stringifyFenceMetadata(metadata); return meta ? `\`\`\`${lang}${meta}` : `\`\`\`${lang}`; } function metadataEquals(a, b) { if (a.leading !== b.leading || a.trailing !== b.trailing) { return false; } if (a.tokens.length !== b.tokens.length) { return false; } for (let i = 0; i < a.tokens.length; i++) { const left = a.tokens[i]; const right = b.tokens[i]; if (left.type !== right.type) { return false; } if (left.type === 'text') { if (left.raw !== right.raw) { return false; } } else { const leftKeys = Object.keys(left.entries).sort(); const rightKeys = Object.keys(right.entries).sort(); if (leftKeys.length !== rightKeys.length) { return false; } for (let j = 0; j < leftKeys.length; j++) { if (leftKeys[j] !== rightKeys[j]) { return false; } const lValues = getSortedUniqueNumbers(left.entries[leftKeys[j]]); const rValues = getSortedUniqueNumbers(right.entries[rightKeys[j]]); if (lValues.length !== rValues.length) { return false; } for (let k = 0; k < lValues.length; k++) { if (lValues[k] !== rValues[k]) { return false; } } } } } return true; } function normalizeMetadata(metadata) { const normalized = cloneMetadata(metadata); normalized.hadDuplicateExpectedErrors = false; normalized.parseError = false; if (!normalized.tokens.length) { normalized.leading = ''; normalized.trailing = ''; } return normalized; } function setCompilerExpectedLines(metadata, lines) { const normalizedLines = getSortedUniqueNumbers(lines); if (normalizedLines.length === 0) { return removeCompilerExpectedLines(metadata); } const next = cloneMetadata(metadata); let token = findExpectedErrorsToken(next); if (!token) { token = {type: 'expectedErrors', entries: {}}; next.tokens = [token, ...next.tokens]; } token.entries[REACT_COMPILER_KEY] = normalizedLines; return normalizeMetadata(next); } function removeCompilerExpectedLines(metadata) { const next = cloneMetadata(metadata); const token = findExpectedErrorsToken(next); if (!token) { return normalizeMetadata(next); } delete token.entries[REACT_COMPILER_KEY]; const hasEntries = Object.values(token.entries).some( (value) => Array.isArray(value) && value.length > 0 ); if (!hasEntries) { next.tokens = next.tokens.filter((item) => item !== token); } return normalizeMetadata(next); } module.exports = { buildFenceLine, getCompilerExpectedLines, getSortedUniqueNumbers, hasCompilerEntry, metadataEquals, metadataHasExpectedErrorsToken, parseFenceMetadata, removeCompilerExpectedLines, setCompilerExpectedLines, stringifyFenceMetadata, }; ================================================ FILE: eslint-local-rules/rules/react-compiler.js ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ const {transformFromAstSync} = require('@babel/core'); const {parse: babelParse} = require('@babel/parser'); const BabelPluginReactCompiler = require('babel-plugin-react-compiler').default; const { parsePluginOptions, validateEnvironmentConfig, } = require('babel-plugin-react-compiler'); const COMPILER_OPTIONS = { noEmit: true, panicThreshold: 'none', environment: validateEnvironmentConfig({ validateRefAccessDuringRender: true, validateNoSetStateInRender: true, validateNoSetStateInEffects: true, validateNoJSXInTryStatements: true, validateNoImpureFunctionsInRender: true, validateStaticComponents: true, validateNoFreezingKnownMutableFunctions: true, validateNoVoidUseMemo: true, validateNoCapitalizedCalls: [], validateHooksUsage: true, validateNoDerivedComputationsInEffects: true, }), }; function hasRelevantCode(code) { const functionPattern = /^(export\s+)?(default\s+)?function\s+\w+/m; const arrowPattern = /^(export\s+)?(const|let|var)\s+\w+\s*=\s*(\([^)]*\)|\w+)\s*=>/m; const hasImports = /^import\s+/m.test(code); return functionPattern.test(code) || arrowPattern.test(code) || hasImports; } function runReactCompiler(code, filename) { const result = { sourceCode: code, events: [], }; if (!hasRelevantCode(code)) { return {...result, diagnostics: []}; } const options = parsePluginOptions({ ...COMPILER_OPTIONS, }); options.logger = { logEvent: (_, event) => { if (event.kind === 'CompileError') { const category = event.detail?.category; if (category === 'Todo' || category === 'Invariant') { return; } result.events.push(event); } }, }; try { const ast = babelParse(code, { sourceFilename: filename, sourceType: 'module', plugins: ['jsx', 'typescript'], }); transformFromAstSync(ast, code, { filename, highlightCode: false, retainLines: true, plugins: [[BabelPluginReactCompiler, options]], sourceType: 'module', configFile: false, babelrc: false, }); } catch (error) { return {...result, diagnostics: []}; } const diagnostics = []; for (const event of result.events) { if (event.kind !== 'CompileError') { continue; } const detail = event.detail; if (!detail) { continue; } const loc = typeof detail.primaryLocation === 'function' ? detail.primaryLocation() : null; if (loc == null || typeof loc === 'symbol') { continue; } const message = typeof detail.printErrorMessage === 'function' ? detail.printErrorMessage(result.sourceCode, {eslint: true}) : detail.description || 'Unknown React Compiler error'; diagnostics.push({detail, loc, message}); } return {...result, diagnostics}; } module.exports = { hasRelevantCode, runReactCompiler, }; ================================================ FILE: next-env.d.ts ================================================ /// <reference types="next" /> /// <reference types="next/image-types/global" /> // NOTE: This file should not be edited // see https://nextjs.org/docs/pages/api-reference/config/typescript for more information. ================================================ FILE: next.config.js ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ /** * @type {import('next').NextConfig} **/ const nextConfig = { pageExtensions: ['jsx', 'js', 'ts', 'tsx', 'mdx', 'md'], reactStrictMode: true, experimental: { scrollRestoration: true, reactCompiler: true, }, async rewrites() { return { beforeFiles: [ // Serve markdown when Accept header prefers text/markdown // Useful for LLM agents - https://www.skeptrune.com/posts/use-the-accept-header-to-serve-markdown-instead-of-html-to-llms/ { source: '/:path((?!llms.txt).*)', has: [ { type: 'header', key: 'accept', value: '(.*text/markdown.*)', }, ], destination: '/api/md/:path*', }, // Explicit .md extension also serves markdown { source: '/:path*.md', destination: '/api/md/:path*', }, ], }; }, env: {}, webpack: (config, {dev, isServer, ...options}) => { if (process.env.ANALYZE) { const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer'); config.plugins.push( new BundleAnalyzerPlugin({ analyzerMode: 'static', reportFilename: options.isServer ? '../analyze/server.html' : './analyze/client.html', }) ); } // Don't bundle the shim unnecessarily. config.resolve.alias['use-sync-external-store/shim'] = 'react'; // ESLint depends on the CommonJS version of esquery, // but Webpack loads the ESM version by default. This // alias ensures the correct version is used. // // More info: // https://github.com/reactjs/react.dev/pull/8115 config.resolve.alias['esquery'] = 'esquery/dist/esquery.min.js'; const {IgnorePlugin, NormalModuleReplacementPlugin} = require('webpack'); config.plugins.push( new NormalModuleReplacementPlugin( /^raf$/, require.resolve('./src/utils/rafShim.js') ), new NormalModuleReplacementPlugin( /^process$/, require.resolve('./src/utils/processShim.js') ), new IgnorePlugin({ checkResource(resource, context) { if ( /\/eslint\/lib\/rules$/.test(context) && /\.\/[\w-]+(\.js)?$/.test(resource) ) { // Skips imports of built-in rules that ESLint // tries to carry into the bundle by default. // We only want the engine and the React rules. return true; } return false; }, }) ); return config; }, }; module.exports = nextConfig; ================================================ FILE: package.json ================================================ { "name": "react-dev", "version": "1.0.0", "private": true, "license": "CC", "scripts": { "analyze": "ANALYZE=true next build", "dev": "next-remote-watch ./src/content", "build": "next build && node --experimental-modules ./scripts/downloadFonts.mjs", "lint": "next lint && eslint \"src/content/**/*.md\"", "lint:fix": "next lint --fix && eslint \"src/content/**/*.md\" --fix", "format:source": "prettier --config .prettierrc --write \"{plugins,src}/**/*.{js,ts,jsx,tsx,css}\"", "nit:source": "prettier --config .prettierrc --list-different \"{plugins,src}/**/*.{js,ts,jsx,tsx,css}\"", "prettier": "yarn format:source", "prettier:diff": "yarn nit:source", "lint-heading-ids": "node scripts/headingIdLinter.js", "fix-headings": "node scripts/headingIdLinter.js --fix", "ci-check": "npm-run-all prettier:diff --parallel lint tsc lint-heading-ids rss deadlinks", "tsc": "tsc --noEmit", "start": "next start", "postinstall": "yarn --cwd eslint-local-rules install && is-ci || husky install .husky", "check-all": "npm-run-all prettier lint:fix tsc rss", "rss": "node scripts/generateRss.js", "deadlinks": "node scripts/deadLinkChecker.js", "copyright": "node scripts/copyright.js", "test:eslint-local-rules": "yarn --cwd eslint-local-rules test", "textlint": "cd textlint && yarn --frozen-lockfile && yarn textlint", "textlint-staged": "cd textlint && yarn --frozen-lockfile && yarn textlint-staged --" }, "dependencies": { "@codesandbox/sandpack-react": "2.13.5", "@docsearch/css": "^3.8.3", "@docsearch/react": "^3.8.3", "@headlessui/react": "^1.7.0", "@radix-ui/react-context-menu": "^2.1.5", "body-scroll-lock": "^3.1.3", "classnames": "^2.2.6", "debounce": "^1.2.1", "github-slugger": "^1.3.0", "next": "15.1.12", "next-remote-watch": "^1.0.0", "parse-numeric-range": "^1.2.0", "react": "^19.0.0", "react-collapsed": "4.0.4", "react-dom": "^19.0.0", "remark-frontmatter": "^4.0.1", "remark-gfm": "^3.0.1" }, "devDependencies": { "@babel/core": "^7.12.9", "@babel/plugin-transform-modules-commonjs": "^7.18.6", "@babel/preset-react": "^7.18.6", "@mdx-js/mdx": "^2.1.3", "@types/body-scroll-lock": "^2.6.1", "@types/classnames": "^2.2.10", "@types/debounce": "^1.2.1", "@types/github-slugger": "^1.3.0", "@types/mdx-js__react": "^1.5.2", "@types/node": "^14.6.4", "@types/parse-numeric-range": "^0.0.1", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", "@typescript-eslint/eslint-plugin": "^5.36.2", "@typescript-eslint/parser": "^5.36.2", "asyncro": "^3.0.0", "autoprefixer": "^10.4.2", "babel-eslint": "10.x", "babel-plugin-react-compiler": "^1.0.0", "chalk": "4.1.2", "eslint": "7.x", "eslint-config-next": "12.0.3", "eslint-config-react-app": "^5.2.1", "eslint-plugin-flowtype": "4.x", "eslint-plugin-import": "2.x", "eslint-plugin-jsx-a11y": "6.x", "eslint-plugin-local-rules": "link:eslint-local-rules", "eslint-plugin-react": "7.x", "eslint-plugin-react-compiler": "^19.0.0-beta-e552027-20250112", "eslint-plugin-react-hooks": "^0.0.0-experimental-fabef7a6b-20221215", "fs-extra": "^9.0.1", "globby": "^11.0.1", "gray-matter": "^4.0.2", "husky": "^7.0.4", "is-ci": "^3.0.1", "lint-staged": ">=10", "mdast-util-to-string": "^1.1.0", "metro-cache": "0.72.2", "npm-run-all": "^4.1.5", "postcss": "^8.4.5", "postcss-flexbugs-fixes": "4.2.1", "postcss-preset-env": "^6.7.0", "prettier": "^2.5.1", "reading-time": "^1.2.0", "remark": "^12.0.1", "remark-external-links": "^7.0.0", "remark-html": "^12.0.0", "remark-images": "^2.0.0", "remark-slug": "^7.0.0", "remark-unwrap-images": "^2.0.0", "retext": "^7.0.1", "retext-smartypants": "^4.0.0", "rss": "^1.2.2", "tailwindcss": "^3.4.1", "typescript": "^5.7.2", "unist-util-visit": "^2.0.3", "webpack-bundle-analyzer": "^4.5.0" }, "engines": { "node": ">=16.8.0" }, "nextBundleAnalysis": { "budget": null, "budgetPercentIncreaseRed": 10, "showDetails": true }, "lint-staged": { "*.{js,ts,jsx,tsx,css}": "yarn prettier", "src/**/*.md": ["yarn fix-headings", "yarn textlint-staged --"] }, "packageManager": "yarn@1.22.22" } ================================================ FILE: plugins/gatsby-remark-japanese-fix/index.js ================================================ const toString = require('mdast-util-to-string'); const visit = require('unist-util-visit'); const hasJapaneseCharacter = (str) => { // Detects katakana, hiragana, iteration marks, and CJK unified ideographs return /[\u30a0-\u30ff\u3040-\u309f\u3005-\u3006\u4e00-\u9fea。、]/.test(str); }; /** * Iterates over emphases (<em>'s) within the AST created by remark. * Applies 'em-ja' class only when it contains a Japanese character, * so that it can be displayed with a different style. */ module.exports = ({markdownAST}, options) => { visit(markdownAST, 'emphasis', (node) => { const nodeStr = toString(node); if (hasJapaneseCharacter(nodeStr)) { // Patch AST node.data = node.data || {}; node.data.hProperties = node.data.hProperties || {}; node.data.hProperties.class = 'em-ja'; } }); return markdownAST; }; ================================================ FILE: plugins/gatsby-remark-japanese-fix/package.json ================================================ { "name": "gatsby-remark-japanese-fix", "version": "0.0.1" } ================================================ FILE: plugins/markdownToHtml.js ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ const remark = require('remark'); const externalLinks = require('remark-external-links'); // Add _target and rel to external links const customHeaders = require('./remark-header-custom-ids'); // Custom header id's for i18n const images = require('remark-images'); // Improved image syntax const unrwapImages = require('remark-unwrap-images'); // Removes <p> wrapper around images const smartyPants = require('./remark-smartypants'); // Cleans up typography const html = require('remark-html'); module.exports = { remarkPlugins: [ externalLinks, customHeaders, images, unrwapImages, smartyPants, ], markdownToHtml, }; async function markdownToHtml(markdown) { const result = await remark() .use(externalLinks) .use(customHeaders) .use(images) .use(unrwapImages) .use(smartyPants) .use(html) .process(markdown); return result.toString(); } ================================================ FILE: plugins/remark-header-custom-ids.js ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /*! * Based on 'gatsby-remark-autolink-headers' * Original Author: Kyle Mathews <mathews.kyle@gmail.com> * Updated by Jared Palmer; * Copyright (c) 2015 Gatsbyjs */ const toString = require('mdast-util-to-string'); const visit = require('unist-util-visit'); const toSlug = require('github-slugger').slug; function patch(context, key, value) { if (!context[key]) { context[key] = value; } return context[key]; } const svgIcon = `<svg aria-hidden="true" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>`; module.exports = ({icon = svgIcon, className = `anchor`} = {}) => { return function transformer(tree) { const ids = new Set(); visit(tree, 'heading', (node) => { let children = [...node.children]; let id; if (children[children.length - 1].type === 'mdxTextExpression') { // # My header {/*my-header*/} id = children.pop().value; const isValidCustomId = id.startsWith('/*') && id.endsWith('*/'); if (!isValidCustomId) { throw Error( 'Expected header ID to be like: {/*some-header*/}. ' + 'Instead, received: ' + id ); } id = id.slice(2, id.length - 2); if (id !== toSlug(id)) { throw Error( 'Expected header ID to be a valid slug. You specified: {/*' + id + '*/}. Replace it with: {/*' + toSlug(id) + '*/}' ); } } else { // # My header id = toSlug(toString(node)); } if (ids.has(id)) { throw Error( 'Cannot have a duplicate header with id "' + id + '" on the page. ' + 'Rename the section or give it an explicit unique ID. ' + 'For example: #### Arguments {/*setstate-arguments*/}' ); } ids.add(id); const data = patch(node, 'data', {}); patch(data, 'id', id); patch(data, 'htmlAttributes', {}); patch(data, 'hProperties', {}); patch(data.htmlAttributes, 'id', id); patch(data.hProperties, 'id', id); }); }; }; ================================================ FILE: plugins/remark-smartypants.js ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /*! * Based on 'silvenon/remark-smartypants' * https://github.com/silvenon/remark-smartypants/pull/80 */ const visit = require('unist-util-visit'); const retext = require('retext'); const smartypants = require('retext-smartypants'); function check(node, parent) { if (node.data?.skipSmartyPants) return false; if (parent.tagName === 'script') return false; if (parent.tagName === 'style') return false; return true; } function markSkip(node) { if (!node) return; node.data ??= {}; node.data.skipSmartyPants = true; if (Array.isArray(node.children)) { for (const child of node.children) { markSkip(child); } } } module.exports = function (options) { const processor = retext().use(smartypants, { ...options, // Do not replace ellipses, dashes, backticks because they change string // length, and we couldn't guarantee right splice of text in second visit of // tree ellipses: false, dashes: false, backticks: false, }); const processor2 = retext().use(smartypants, { ...options, // Do not replace quotes because they are already replaced in the first // processor quotes: false, }); function transformer(tree) { let allText = ''; let startIndex = 0; const textOrInlineCodeNodes = []; visit(tree, 'mdxJsxFlowElement', (node) => { if (['TerminalBlock'].includes(node.name)) { markSkip(node); // Mark all children to skip smarty pants } }); visit(tree, ['text', 'inlineCode'], (node, _, parent) => { if (check(node, parent)) { if (node.type === 'text') allText += node.value; // for the case when inlineCode contains just one part of quote: `foo'bar` else allText += 'A'.repeat(node.value.length); textOrInlineCodeNodes.push(node); } }); // Concat all text into one string, to properly replace quotes around non-"text" nodes allText = String(processor.processSync(allText)); for (const node of textOrInlineCodeNodes) { const endIndex = startIndex + node.value.length; if (node.type === 'text') { const processedText = allText.slice(startIndex, endIndex); node.value = String(processor2.processSync(processedText)); } startIndex = endIndex; } } return transformer; }; ================================================ FILE: postcss.config.js ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ module.exports = { plugins: { tailwindcss: {}, autoprefixer: {}, 'postcss-flexbugs-fixes': {}, 'postcss-preset-env': { autoprefixer: { flexbox: 'no-2009', }, stage: 3, features: { 'custom-properties': false, }, }, }, }; ================================================ FILE: public/.well-known/atproto-did ================================================ did:plc:uorpbnp2q32vuvyeruwauyhe ================================================ FILE: public/browserconfig.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <browserconfig> <msapplication> <tile> <square150x150logo src="/mstile-150x150.png"/> <TileColor>#2b5797</TileColor> </tile> </msapplication> </browserconfig> ================================================ FILE: public/html/single-file-example.html ================================================ <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>Hello World
    ================================================ FILE: public/js/jsfiddle-integration-babel.js ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ // Do not delete or move this file. // Many fiddles reference it so we have to keep it here. (function () { var tag = document.querySelector( 'script[type="application/javascript;version=1.7"]' ); if (!tag || tag.textContent.indexOf('window.onload=function(){') !== -1) { alert( 'Bad JSFiddle configuration, please fork the original React JSFiddle' ); } tag.setAttribute('type', 'text/babel'); tag.textContent = tag.textContent.replace(/^\/\/ { try { const result = processFile(file); if (result != null) { updatedFiles.set(file, result); } } catch (e) { console.error(e); hasErrors = true; } }); if (hasErrors) { console.error('Update failed'); process.exit(1); } else { for (const [file, source] of updatedFiles) { fs.writeFileSync(file, source, 'utf8'); } console.log('Update complete'); } function processFile(file) { if (fs.lstatSync(file).isDirectory()) { return; } let source = fs.readFileSync(file, 'utf8'); let shebang = ''; if (source.startsWith('#!')) { const newlineIndex = source.indexOf('\n'); if (newlineIndex === -1) { shebang = `${source}\n`; source = ''; } else { shebang = source.slice(0, newlineIndex + 1); source = source.slice(newlineIndex + 1); } } if (source.indexOf(META_COPYRIGHT_COMMENT_BLOCK) === 0) { return null; } if (/^\/\*\*/.test(source)) { source = source.replace(/\/\*\*[^\/]+\/\s+/, META_COPYRIGHT_COMMENT_BLOCK); } else { source = `${META_COPYRIGHT_COMMENT_BLOCK}${source}`; } if (shebang) { return `${shebang}${source}`; } return source; } ================================================ FILE: scripts/deadLinkChecker.js ================================================ #!/usr/bin/env node /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ const fs = require('fs'); const path = require('path'); const globby = require('globby'); const chalk = require('chalk'); const CONTENT_DIR = path.join(__dirname, '../src/content'); const PUBLIC_DIR = path.join(__dirname, '../public'); const fileCache = new Map(); const anchorMap = new Map(); // Map> const contributorMap = new Map(); // Map const redirectMap = new Map(); // Map let errorCodes = new Set(); async function readFileWithCache(filePath) { if (!fileCache.has(filePath)) { try { const content = await fs.promises.readFile(filePath, 'utf8'); fileCache.set(filePath, content); } catch (error) { throw new Error(`Failed to read file ${filePath}: ${error.message}`); } } return fileCache.get(filePath); } async function fileExists(filePath) { try { await fs.promises.access(filePath, fs.constants.R_OK); return true; } catch { return false; } } function getMarkdownFiles() { // Convert Windows paths to POSIX for globby compatibility const baseDir = CONTENT_DIR.replace(/\\/g, '/'); const patterns = [ path.posix.join(baseDir, '**/*.md'), path.posix.join(baseDir, '**/*.mdx'), ]; return globby.sync(patterns); } function extractAnchorsFromContent(content) { const anchors = new Set(); // MDX-style heading IDs: {/*anchor-id*/} const mdxPattern = /\{\/\*([a-zA-Z0-9-_]+)\*\/\}/g; let match; while ((match = mdxPattern.exec(content)) !== null) { anchors.add(match[1].toLowerCase()); } // HTML id attributes const htmlIdPattern = /\sid=["']([a-zA-Z0-9-_]+)["']/g; while ((match = htmlIdPattern.exec(content)) !== null) { anchors.add(match[1].toLowerCase()); } // Markdown heading with explicit ID: ## Heading {#anchor-id} const markdownHeadingPattern = /^#+\s+.*\{#([a-zA-Z0-9-_]+)\}/gm; while ((match = markdownHeadingPattern.exec(content)) !== null) { anchors.add(match[1].toLowerCase()); } return anchors; } async function buildAnchorMap(files) { for (const filePath of files) { const content = await readFileWithCache(filePath); const anchors = extractAnchorsFromContent(content); if (anchors.size > 0) { anchorMap.set(filePath, anchors); } } } function extractLinksFromContent(content) { const linkPattern = /\[([^\]]*)\]\(([^)]+)\)/g; const links = []; let match; while ((match = linkPattern.exec(content)) !== null) { const [, linkText, linkUrl] = match; if (linkUrl.startsWith('/') && !linkUrl.startsWith('//')) { const lines = content.substring(0, match.index).split('\n'); const line = lines.length; const lastLineStart = lines.length > 1 ? content.lastIndexOf('\n', match.index - 1) + 1 : 0; const column = match.index - lastLineStart + 1; links.push({ text: linkText, url: linkUrl, line, column, }); } } return links; } async function findTargetFile(urlPath) { // Check if it's an image or static asset that might be in the public directory const imageExtensions = [ '.png', '.jpg', '.jpeg', '.gif', '.svg', '.ico', '.webp', ]; const hasImageExtension = imageExtensions.some((ext) => urlPath.toLowerCase().endsWith(ext) ); if (hasImageExtension || urlPath.includes('.')) { // Check in public directory (with and without leading slash) const publicPaths = [ path.join(PUBLIC_DIR, urlPath), path.join(PUBLIC_DIR, urlPath.substring(1)), ]; for (const p of publicPaths) { if (await fileExists(p)) { return p; } } } const possiblePaths = [ path.join(CONTENT_DIR, urlPath + '.md'), path.join(CONTENT_DIR, urlPath + '.mdx'), path.join(CONTENT_DIR, urlPath, 'index.md'), path.join(CONTENT_DIR, urlPath, 'index.mdx'), // Without leading slash path.join(CONTENT_DIR, urlPath.substring(1) + '.md'), path.join(CONTENT_DIR, urlPath.substring(1) + '.mdx'), path.join(CONTENT_DIR, urlPath.substring(1), 'index.md'), path.join(CONTENT_DIR, urlPath.substring(1), 'index.mdx'), ]; for (const p of possiblePaths) { if (await fileExists(p)) { return p; } } return null; } async function validateLink(link) { const urlAnchorPattern = /#([a-zA-Z0-9-_]+)$/; const anchorMatch = link.url.match(urlAnchorPattern); const urlWithoutAnchor = link.url.replace(urlAnchorPattern, ''); if (urlWithoutAnchor === '/') { return {valid: true}; } // Check for redirects if (redirectMap.has(urlWithoutAnchor)) { const redirectDestination = redirectMap.get(urlWithoutAnchor); if ( redirectDestination.startsWith('http://') || redirectDestination.startsWith('https://') ) { return {valid: true}; } const redirectedLink = { ...link, url: redirectDestination + (anchorMatch ? anchorMatch[0] : ''), }; return validateLink(redirectedLink); } // Check if it's an error code link const errorCodeMatch = urlWithoutAnchor.match(/^\/errors\/(\d+)$/); if (errorCodeMatch) { const code = errorCodeMatch[1]; if (!errorCodes.has(code)) { return { valid: false, reason: `Error code ${code} not found in React error codes`, }; } return {valid: true}; } // Check if it's a contributor link on the team or acknowledgements page if ( anchorMatch && (urlWithoutAnchor === '/community/team' || urlWithoutAnchor === '/community/acknowledgements') ) { const anchorId = anchorMatch[1].toLowerCase(); if (contributorMap.has(anchorId)) { const correctUrl = contributorMap.get(anchorId); if (correctUrl !== link.url) { return { valid: false, reason: `Contributor link should be updated to: ${correctUrl}`, }; } return {valid: true}; } else { return { valid: false, reason: `Contributor link not found`, }; } } const targetFile = await findTargetFile(urlWithoutAnchor); if (!targetFile) { return { valid: false, reason: `Target file not found for: ${urlWithoutAnchor}`, }; } // Only check anchors for content files, not static assets if (anchorMatch && targetFile.startsWith(CONTENT_DIR)) { const anchorId = anchorMatch[1].toLowerCase(); // TODO handle more special cases. These are usually from custom MDX components that include // a Heading from src/components/MDX/Heading.tsx which automatically injects an anchor tag. switch (anchorId) { case 'challenges': case 'recap': { return {valid: true}; } } const fileAnchors = anchorMap.get(targetFile); if (!fileAnchors || !fileAnchors.has(anchorId)) { return { valid: false, reason: `Anchor #${anchorMatch[1]} not found in ${path.relative( CONTENT_DIR, targetFile )}`, }; } } return {valid: true}; } async function processFile(filePath) { const content = await readFileWithCache(filePath); const links = extractLinksFromContent(content); const deadLinks = []; for (const link of links) { const result = await validateLink(link); if (!result.valid) { deadLinks.push({ file: path.relative(process.cwd(), filePath), line: link.line, column: link.column, text: link.text, url: link.url, reason: result.reason, }); } } return {deadLinks, totalLinks: links.length}; } async function buildContributorMap() { const teamFile = path.join(CONTENT_DIR, 'community/team.md'); const teamContent = await readFileWithCache(teamFile); const teamMemberPattern = /]*permalink=["']([^"']+)["']/g; let match; while ((match = teamMemberPattern.exec(teamContent)) !== null) { const permalink = match[1]; contributorMap.set(permalink, `/community/team#${permalink}`); } const ackFile = path.join(CONTENT_DIR, 'community/acknowledgements.md'); const ackContent = await readFileWithCache(ackFile); const contributorPattern = /\*\s*\[([^\]]+)\]\(([^)]+)\)/g; while ((match = contributorPattern.exec(ackContent)) !== null) { const name = match[1]; const url = match[2]; const hyphenatedName = name.toLowerCase().replace(/\s+/g, '-'); if (!contributorMap.has(hyphenatedName)) { contributorMap.set(hyphenatedName, url); } } } async function fetchErrorCodes() { try { const response = await fetch( 'https://raw.githubusercontent.com/facebook/react/main/scripts/error-codes/codes.json' ); if (!response.ok) { throw new Error(`Failed to fetch error codes: ${response.status}`); } const codes = await response.json(); errorCodes = new Set(Object.keys(codes)); console.log(chalk.gray(`Fetched ${errorCodes.size} React error codes`)); } catch (error) { throw new Error(`Failed to fetch error codes: ${error.message}`); } } async function buildRedirectsMap() { try { const vercelConfigPath = path.join(__dirname, '../vercel.json'); const vercelConfig = JSON.parse( await fs.promises.readFile(vercelConfigPath, 'utf8') ); if (vercelConfig.redirects) { for (const redirect of vercelConfig.redirects) { redirectMap.set(redirect.source, redirect.destination); } console.log( chalk.gray(`Loaded ${redirectMap.size} redirects from vercel.json`) ); } } catch (error) { console.log( chalk.yellow( `Warning: Could not load redirects from vercel.json: ${error.message}\n` ) ); } } async function main() { const files = getMarkdownFiles(); console.log(chalk.gray(`Checking ${files.length} markdown files...`)); await fetchErrorCodes(); await buildRedirectsMap(); await buildContributorMap(); await buildAnchorMap(files); const filePromises = files.map((filePath) => processFile(filePath)); const results = await Promise.all(filePromises); const deadLinks = results.flatMap((r) => r.deadLinks); const totalLinks = results.reduce((sum, r) => sum + r.totalLinks, 0); if (deadLinks.length > 0) { console.log('\n'); for (const link of deadLinks) { console.log(chalk.yellow(`${link.file}:${link.line}:${link.column}`)); console.log(chalk.reset(` Link text: ${link.text}`)); console.log(chalk.reset(` URL: ${link.url}`)); console.log(` ${chalk.red('✗')} ${chalk.red(link.reason)}\n`); } console.log( chalk.red( `\nFound ${deadLinks.length} dead link${ deadLinks.length > 1 ? 's' : '' } out of ${totalLinks} total links\n` ) ); process.exit(1); } console.log(chalk.green(`\n✓ All ${totalLinks} links are valid!\n`)); process.exit(0); } main().catch((error) => { console.log(chalk.red(`Error: ${error.message}`)); process.exit(1); }); ================================================ FILE: scripts/downloadFonts.mjs ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. */ import { exec } from 'child_process'; import { mkdir, promises as fsPromises } from 'fs'; import { dirname } from 'path'; import { promisify } from 'util'; const execAsync = promisify(exec); // Taken from Downloads on https://www.facebook.com/brand/meta/typography/. // To refresh the list, go to the Conf website's public/fonts/ folder and run this: // printf "\n[\n%s\n]\n" "$(find . -type f ! -path "*/.*" -name "*.woff2" | sed 's|^./||' | sort | awk '{printf " \"%s\",\n", $0}' | sed '$s/,$//')" const paths = [ "Optimistic_Display_Arbc_W_Bd.woff2", "Optimistic_Display_Arbc_W_Md.woff2", "Optimistic_Display_Arbc_W_SBd.woff2", "Optimistic_Display_Cyrl_W_Bd.woff2", "Optimistic_Display_Cyrl_W_Md.woff2", "Optimistic_Display_Cyrl_W_SBd.woff2", "Optimistic_Display_Deva_W_Bd.woff2", "Optimistic_Display_Deva_W_Md.woff2", "Optimistic_Display_Deva_W_SBd.woff2", "Optimistic_Display_Viet_W_Bd.woff2", "Optimistic_Display_Viet_W_Md.woff2", "Optimistic_Display_Viet_W_SBd.woff2", "Optimistic_Display_W_Bd.woff2", "Optimistic_Display_W_BdIt.woff2", "Optimistic_Display_W_Lt.woff2", "Optimistic_Display_W_Md.woff2", "Optimistic_Display_W_MdIt.woff2", "Optimistic_Display_W_SBd.woff2", "Optimistic_Display_W_SBdIt.woff2", "Optimistic_Display_W_XBd.woff2", "Optimistic_Display_W_XLt.woff2", "Optimistic_Text_Arbc_W_Bd.woff2", "Optimistic_Text_Arbc_W_Md.woff2", "Optimistic_Text_Arbc_W_Rg.woff2", "Optimistic_Text_Arbc_W_XBd.woff2", "Optimistic_Text_Cyrl_W_Bd.woff2", "Optimistic_Text_Cyrl_W_Md.woff2", "Optimistic_Text_Cyrl_W_Rg.woff2", "Optimistic_Text_Cyrl_W_XBd.woff2", "Optimistic_Text_Deva_W_Bd.woff2", "Optimistic_Text_Deva_W_Md.woff2", "Optimistic_Text_Deva_W_Rg.woff2", "Optimistic_Text_Deva_W_XBd.woff2", "Optimistic_Text_Viet_W_Bd.woff2", "Optimistic_Text_Viet_W_Md.woff2", "Optimistic_Text_Viet_W_Rg.woff2", "Optimistic_Text_Viet_W_XBd.woff2", "Optimistic_Text_W_Bd.woff2", "Optimistic_Text_W_BdIt.woff2", "Optimistic_Text_W_It.woff2", "Optimistic_Text_W_Md.woff2", "Optimistic_Text_W_MdIt.woff2", "Optimistic_Text_W_Rg.woff2", "Optimistic_Text_W_XBd.woff2", "Optimistic_Text_W_XBdIt.woff2" ]; const baseURL = "https://conf.reactjs.org/fonts/"; const outputDir = "public/fonts/"; await Promise.all( paths.map(async (path) => { const localPath = `${outputDir}${path}`; const localDir = dirname(localPath); await fsPromises.mkdir(localDir, { recursive: true }); const command = `curl ${baseURL}${path} --output ${localPath}`; await execAsync(command); console.log(`Downloaded ${path}`); }) ); console.log("All fonts downloaded."); ================================================ FILE: scripts/generateRss.js ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ const {generateRssFeed} = require('../src/utils/rss'); generateRssFeed(); ================================================ FILE: scripts/headingIDHelpers/generateHeadingIDs.js ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ // To do: Make this ESM. // To do: properly check heading numbers (headings with the same text get // numbered, this script doesn’t check that). const assert = require('assert'); const fs = require('fs'); const GithubSlugger = require('github-slugger'); const walk = require('./walk'); let modules; function stripLinks(line) { return line.replace(/\[([^\]]+)\]\([^)]+\)/, (match, p1) => p1); } function addHeaderID(line, slugger) { // check if we're a header at all if (!line.startsWith('#')) { return line; } const match = /^(#+\s+)(.+?)(\s*\{(?:\/\*|#)([^\}\*\/]+)(?:\*\/)?\}\s*)?$/.exec(line); const before = match[1] + match[2]; const proc = modules .unified() .use(modules.remarkParse) .use(modules.remarkSlug); const tree = proc.runSync(proc.parse(before)); const head = tree.children[0]; assert( head && head.type === 'heading', 'expected `' + before + '` to be a heading, is it using a normal space after `#`?' ); const autoId = head.data.id; const existingId = match[4]; const id = existingId || autoId; // Ignore numbers: const cleanExisting = existingId ? existingId.replace(/-\d+$/, '') : undefined; const cleanAuto = autoId.replace(/-\d+$/, ''); if (cleanExisting && cleanExisting !== cleanAuto) { console.log( 'Note: heading `%s` has a different ID (`%s`) than what GH generates for it: `%s`:', before, existingId, autoId ); } return match[1] + match[2] + ' {/*' + id + '*/}'; } function addHeaderIDs(lines) { // Sluggers should be per file const slugger = new GithubSlugger(); let inCode = false; const results = []; lines.forEach((line) => { // Ignore code blocks if (line.startsWith('```')) { inCode = !inCode; results.push(line); return; } if (inCode) { results.push(line); return; } results.push(addHeaderID(line, slugger)); }); return results; } async function main(paths) { paths = paths.length === 0 ? ['src/content'] : paths; const [unifiedMod, remarkParseMod, remarkSlugMod] = await Promise.all([ import('unified'), import('remark-parse'), import('remark-slug'), ]); const unified = unifiedMod.unified; const remarkParse = remarkParseMod.default; const remarkSlug = remarkSlugMod.default; modules = {unified, remarkParse, remarkSlug}; const files = paths.map((path) => [...walk(path)]).flat(); files.forEach((file) => { if (!(file.endsWith('.md') || file.endsWith('.mdx'))) { return; } const content = fs.readFileSync(file, 'utf8'); const lines = content.split('\n'); const updatedLines = addHeaderIDs(lines); fs.writeFileSync(file, updatedLines.join('\n')); }); } module.exports = main; ================================================ FILE: scripts/headingIDHelpers/validateHeadingIDs.js ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ const fs = require('fs'); const walk = require('./walk'); /** * Validate if there is a custom heading id and exit if there isn't a heading * @param {string} line * @returns */ function validateHeaderId(line) { if (!line.startsWith('#')) { return; } const match = /\{\/\*(.*?)\*\/}/.exec(line); const id = match; if (!id) { console.error('Run yarn fix-headings to generate headings.'); process.exit(1); } } /** * Loops through the lines to skip code blocks * @param {Array} lines */ function validateHeaderIds(lines) { let inCode = false; const results = []; lines.forEach((line) => { // Ignore code blocks if (line.startsWith('```')) { inCode = !inCode; results.push(line); return; } if (inCode) { results.push(line); return; } validateHeaderId(line); }); } /** * paths are basically array of path for which we have to validate heading IDs * @param {Array} paths */ async function main(paths) { paths = paths.length === 0 ? ['src/content'] : paths; const files = paths.map((path) => [...walk(path)]).flat(); files.forEach((file) => { if (!(file.endsWith('.md') || file.endsWith('.mdx'))) { return; } const content = fs.readFileSync(file, 'utf8'); const lines = content.split('\n'); validateHeaderIds(lines); }); } module.exports = main; ================================================ FILE: scripts/headingIDHelpers/walk.js ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ const fs = require('fs'); module.exports = function walk(dir) { let results = []; /** * If the param is a directory we can return the file */ if (dir.includes('md')) { return [dir]; } const list = fs.readdirSync(dir); list.forEach(function (file) { file = dir + '/' + file; const stat = fs.statSync(file); if (stat && stat.isDirectory()) { /* Recurse into a subdirectory */ results = results.concat(walk(file)); } else { /* Is a file */ results.push(file); } }); return results; }; ================================================ FILE: scripts/headingIdLinter.js ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ const validateHeaderIds = require('./headingIDHelpers/validateHeadingIDs'); const generateHeadingIds = require('./headingIDHelpers/generateHeadingIDs'); /** * yarn lint-heading-ids --> Checks all files and causes an error if heading ID is missing * yarn lint-heading-ids --fix --> Fixes all markdown file's heading IDs * yarn lint-heading-ids path/to/markdown.md --> Checks that particular file for missing heading ID (path can denote a directory or particular file) * yarn lint-heading-ids --fix path/to/markdown.md --> Fixes that particular file's markdown IDs (path can denote a directory or particular file) */ const markdownPaths = process.argv.slice(2); if (markdownPaths.includes('--fix')) { generateHeadingIds(markdownPaths.filter((path) => path !== '--fix')); } else { validateHeaderIds(markdownPaths); } ================================================ FILE: src/components/Breadcrumbs.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {Fragment} from 'react'; import Link from 'next/link'; import type {RouteItem} from 'components/Layout/getRouteMeta'; function Breadcrumbs({breadcrumbs}: {breadcrumbs: RouteItem[]}) { return (
    {breadcrumbs.map( (crumb, i) => crumb.path && !crumb.skipBreadcrumb && (
    {crumb.title}
    ) )}
    ); } export default Breadcrumbs; ================================================ FILE: src/components/Button.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import * as React from 'react'; import cn from 'classnames'; interface ButtonProps { children: React.ReactNode; onClick?: (event: React.MouseEvent) => void; active?: boolean; className?: string; style?: Record; } export function Button({ children, onClick, active = false, className, style, }: ButtonProps) { return ( ); } export default Button; ================================================ FILE: src/components/ButtonLink.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import cn from 'classnames'; import NextLink from 'next/link'; interface ButtonLinkProps { size?: 'md' | 'lg'; type?: 'primary' | 'secondary'; label?: string; target?: '_self' | '_blank'; } function ButtonLink({ href, className, children, type = 'primary', size = 'md', label, target = '_self', ...props }: React.AnchorHTMLAttributes & ButtonLinkProps) { const classes = cn( className, 'active:scale-[.98] transition-transform inline-flex font-bold items-center outline-none focus:outline-none focus-visible:outline focus-visible:outline-link focus:outline-offset-2 focus-visible:dark:focus:outline-link-dark leading-snug', { 'bg-link text-white dark:bg-brand-dark dark:text-secondary hover:bg-opacity-80': type === 'primary', 'text-primary dark:text-primary-dark shadow-secondary-button-stroke dark:shadow-secondary-button-stroke-dark hover:bg-gray-40/5 active:bg-gray-40/10 hover:dark:bg-gray-60/5 active:dark:bg-gray-60/10': type === 'secondary', 'text-lg py-3 rounded-full px-4 sm:px-6': size === 'lg', 'text-base rounded-full px-4 py-2': size === 'md', } ); return ( {children} ); } export default ButtonLink; ================================================ FILE: src/components/DocsFooter.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import NextLink from 'next/link'; import {memo} from 'react'; import cn from 'classnames'; import {IconNavArrow} from './Icon/IconNavArrow'; import type {RouteMeta} from './Layout/getRouteMeta'; export type DocsPageFooterProps = Pick< RouteMeta, 'route' | 'nextRoute' | 'prevRoute' >; function areEqual(prevProps: DocsPageFooterProps, props: DocsPageFooterProps) { return prevProps.route?.path === props.route?.path; } export const DocsPageFooter = memo( function DocsPageFooter({nextRoute, prevRoute, route}) { if (!route || route?.heading) { return null; } return ( <> {prevRoute?.path || nextRoute?.path ? ( <>
    {prevRoute?.path ? ( ) : (
    )} {nextRoute?.path ? ( ) : (
    )}
    ) : null} ); }, areEqual ); function FooterLink({ href, title, type, }: { href: string; title: string; type: 'Previous' | 'Next'; }) { return (
    {type === 'Previous' ? 'Previous' : 'Next'} {title}
    ); } ================================================ FILE: src/components/ErrorDecoderContext.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ // Error Decoder requires reading pregenerated error message from getStaticProps, // but MDX component doesn't support props. So we use React Context to populate // the value without prop-drilling. // TODO: Replace with React.cache + React.use when migrating to Next.js App Router import {createContext, useContext} from 'react'; const notInErrorDecoderContext = Symbol('not in error decoder context'); export const ErrorDecoderContext = createContext< | {errorMessage: string | null; errorCode: string | null} | typeof notInErrorDecoderContext >(notInErrorDecoderContext); export const useErrorDecoderParams = () => { const params = useContext(ErrorDecoderContext); if (params === notInErrorDecoderContext) { throw new Error('useErrorDecoder must be used in error decoder pages only'); } return params; }; ================================================ FILE: src/components/ExternalLink.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import type {DetailedHTMLProps, AnchorHTMLAttributes} from 'react'; export function ExternalLink({ href, target, children, ...props }: DetailedHTMLProps< AnchorHTMLAttributes, HTMLAnchorElement >) { return ( {children} ); } ================================================ FILE: src/components/Icon/IconArrow.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {memo} from 'react'; import cn from 'classnames'; import type {SVGProps} from 'react'; export const IconArrow = memo< SVGProps & { /** * The direction the arrow should point. * `start` and `end` are relative to the current locale. * for example, in LTR, `start` is left and `end` is right. */ displayDirection: 'start' | 'end' | 'right' | 'left' | 'up' | 'down'; } >(function IconArrow({displayDirection, className, ...rest}) { return ( ); }); ================================================ FILE: src/components/Icon/IconArrowSmall.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {memo} from 'react'; import cn from 'classnames'; import type {SVGProps} from 'react'; export const IconArrowSmall = memo< SVGProps & { /** * The direction the arrow should point. * `start` and `end` are relative to the current locale. * for example, in LTR, `start` is left and `end` is right. */ displayDirection: 'start' | 'end' | 'right' | 'left' | 'up' | 'down'; } >(function IconArrowSmall({displayDirection, className, ...rest}) { const classes = cn(className, { 'rotate-180': displayDirection === 'left', 'rotate-180 rtl:rotate-0': displayDirection === 'start', 'rtl:rotate-180': displayDirection === 'end', 'rotate-90': displayDirection === 'down', }); return ( ); }); ================================================ FILE: src/components/Icon/IconBsky.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {memo} from 'react'; import type {SVGProps} from 'react'; export const IconBsky = memo>(function IconBsky(props) { return ( ); }); ================================================ FILE: src/components/Icon/IconCanary.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {memo} from 'react'; export const IconCanary = memo< JSX.IntrinsicElements['svg'] & {title?: string; size?: 's' | 'md'} >(function IconCanary( {className, title, size} = { className: undefined, title: undefined, size: 'md', } ) { return ( {title && {title}} ); }); ================================================ FILE: src/components/Icon/IconChevron.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {memo} from 'react'; import cn from 'classnames'; export const IconChevron = memo< JSX.IntrinsicElements['svg'] & { /** * The direction the arrow should point. * `start` and `end` are relative to the current locale. * for example, in LTR, `start` is left and `end` is right. */ displayDirection: 'start' | 'end' | 'right' | 'left' | 'up' | 'down'; } >(function IconChevron({className, displayDirection}) { const classes = cn( { 'rotate-0': displayDirection === 'down', 'rotate-90': displayDirection === 'left', 'rotate-180': displayDirection === 'up', '-rotate-90': displayDirection === 'right', 'rotate-90 rtl:-rotate-90': displayDirection === 'start', '-rotate-90 rtl:rotate-90': displayDirection === 'end', }, className ); return ( ); }); ================================================ FILE: src/components/Icon/IconClose.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {memo} from 'react'; import type {SVGProps} from 'react'; export const IconClose = memo>(function IconClose( props ) { return ( ); }); ================================================ FILE: src/components/Icon/IconCodeBlock.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {memo} from 'react'; export const IconCodeBlock = memo( function IconCodeBlock({className}) { return ( ); } ); ================================================ FILE: src/components/Icon/IconCopy.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {memo} from 'react'; export const IconCopy = memo(function IconCopy({ className, }) { return ( {' '} ); }); ================================================ FILE: src/components/Icon/IconDeepDive.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {memo} from 'react'; export const IconDeepDive = memo( function IconDeepDive({className}) { return ( ); } ); ================================================ FILE: src/components/Icon/IconDownload.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {memo} from 'react'; export const IconDownload = memo( function IconDownload({className}) { return ( ); } ); ================================================ FILE: src/components/Icon/IconError.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {memo} from 'react'; export const IconError = memo(function IconError({ className, }) { return ( ); }); ================================================ FILE: src/components/Icon/IconExperimental.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {memo} from 'react'; export const IconExperimental = memo< JSX.IntrinsicElements['svg'] & {title?: string; size?: 's' | 'md'} >(function IconExperimental( {className, title, size} = { className: undefined, title: undefined, size: 'md', } ) { return ( {title && {title}} ); }); ================================================ FILE: src/components/Icon/IconFacebookCircle.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {memo} from 'react'; import type {SVGProps} from 'react'; export const IconFacebookCircle = memo>( function IconFacebookCircle(props) { return ( ); } ); ================================================ FILE: src/components/Icon/IconGitHub.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {memo} from 'react'; import type {SVGProps} from 'react'; export const IconGitHub = memo>(function IconGitHub( props ) { return ( ); }); ================================================ FILE: src/components/Icon/IconHamburger.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {memo} from 'react'; import type {SVGProps} from 'react'; export const IconHamburger = memo>( function IconHamburger(props) { return ( ); } ); ================================================ FILE: src/components/Icon/IconHint.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {memo} from 'react'; import cn from 'classnames'; export const IconHint = memo(function IconHint({ className, }) { return ( ); }); ================================================ FILE: src/components/Icon/IconInstagram.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {memo} from 'react'; import type {SVGProps} from 'react'; export const IconInstagram = memo>( function IconInstagram(props) { return ( ); } ); ================================================ FILE: src/components/Icon/IconLink.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {memo} from 'react'; import type {SVGProps} from 'react'; export const IconLink = memo>(function IconLink(props) { return ( ); }); ================================================ FILE: src/components/Icon/IconNavArrow.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {memo} from 'react'; import cn from 'classnames'; export const IconNavArrow = memo< JSX.IntrinsicElements['svg'] & { /** * The direction the arrow should point. * `start` and `end` are relative to the current locale. * for example, in LTR, `start` is left and `end` is right. */ displayDirection: 'start' | 'end' | 'right' | 'left' | 'down'; } >(function IconNavArrow({displayDirection = 'start', className}) { const classes = cn( 'duration-100 ease-in transition', { 'rotate-0': displayDirection === 'down', 'rotate-90': displayDirection === 'left', '-rotate-90': displayDirection === 'right', 'rotate-90 rtl:-rotate-90': displayDirection === 'start', '-rotate-90 rtl:rotate-90': displayDirection === 'end', }, className ); return ( ); }); ================================================ FILE: src/components/Icon/IconNewPage.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {memo} from 'react'; import type {SVGProps} from 'react'; export const IconNewPage = memo>(function IconNewPage( props ) { return ( ); }); ================================================ FILE: src/components/Icon/IconNote.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {memo} from 'react'; export const IconNote = memo(function IconNote({ className, }) { return ( ); }); ================================================ FILE: src/components/Icon/IconPitfall.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {memo} from 'react'; export const IconPitfall = memo( function IconPitfall({className}) { return ( ); } ); ================================================ FILE: src/components/Icon/IconRestart.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {memo} from 'react'; export const IconRestart = memo( function IconRestart({className}) { return ( ); } ); ================================================ FILE: src/components/Icon/IconRocket.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {memo} from 'react'; export const IconRocket = memo< JSX.IntrinsicElements['svg'] & {title?: string; size?: 's' | 'md'} >(function IconRocket({className, size = 'md'}) { return ( ); }); ================================================ FILE: src/components/Icon/IconRss.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {memo} from 'react'; import type {SVGProps} from 'react'; export const IconRss = memo>(function IconRss(props) { return ( ); }); ================================================ FILE: src/components/Icon/IconSearch.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {memo} from 'react'; import type {SVGProps} from 'react'; export const IconSearch = memo>(function IconSearch( props ) { return ( ); }); ================================================ FILE: src/components/Icon/IconSolution.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {memo} from 'react'; import cn from 'classnames'; export const IconSolution = memo( function IconSolution({className}) { return ( ); } ); ================================================ FILE: src/components/Icon/IconTerminal.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {memo} from 'react'; export const IconTerminal = memo( function IconTerminal({className}) { return ( ); } ); ================================================ FILE: src/components/Icon/IconThreads.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {memo} from 'react'; import type {SVGProps} from 'react'; export const IconThreads = memo>(function IconThreads( props ) { return ( ); }); ================================================ FILE: src/components/Icon/IconTwitter.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {memo} from 'react'; import type {SVGProps} from 'react'; export const IconTwitter = memo>(function IconTwitter( props ) { return ( ); }); ================================================ FILE: src/components/Icon/IconWarning.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {memo} from 'react'; export const IconWarning = memo( function IconWarning({className}) { return ( ); } ); ================================================ FILE: src/components/Layout/Footer.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import * as React from 'react'; import NextLink from 'next/link'; import cn from 'classnames'; import {ExternalLink} from 'components/ExternalLink'; import {IconFacebookCircle} from 'components/Icon/IconFacebookCircle'; import {IconTwitter} from 'components/Icon/IconTwitter'; import {IconBsky} from 'components/Icon/IconBsky'; import {IconGitHub} from 'components/Icon/IconGitHub'; export function Footer() { const socialLinkClasses = 'hover:text-primary dark:text-primary-dark'; return (
    Copyright © Meta Platforms, Inc
    { // @ts-ignore window.__setUwu(false); }}> no uwu plz
    { // @ts-ignore window.__setUwu(true); }}> uwu?
    Logo by @sawaratsuki1004
    React を学ぶ クイックスタート インストール UI の記述 インタラクティビティの追加 state の管理 避難ハッチ
    API リファレンス React APIs React DOM APIs
    コミュニティ 行動規範 チーム紹介 ドキュメント貢献者 謝辞
    More ブログ React Native プライバシー 利用規約
    ); } function FooterLink({ href, children, isHeader = false, }: { href?: string; children: React.ReactNode; isHeader?: boolean; }) { const classes = cn('border-b inline-block border-transparent', { 'text-sm text-primary dark:text-primary-dark': !isHeader, 'text-md text-secondary dark:text-secondary-dark my-2 font-bold': isHeader, 'hover:border-gray-10': href, }); if (!href) { return
    {children}
    ; } if (href.startsWith('https://')) { return (
    {children}
    ); } return (
    {children}
    ); } ================================================ FILE: src/components/Layout/HomeContent.js ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import { createContext, memo, useState, useContext, useId, Suspense, useEffect, useRef, useTransition, } from 'react'; import cn from 'classnames'; import NextLink from 'next/link'; import ButtonLink from '../ButtonLink'; import {IconRestart} from '../Icon/IconRestart'; import BlogCard from 'components/MDX/BlogCard'; import {IconChevron} from 'components/Icon/IconChevron'; import {IconSearch} from 'components/Icon/IconSearch'; import {Logo} from 'components/Logo'; import Link from 'components/MDX/Link'; import CodeBlock from 'components/MDX/CodeBlock'; import {ExternalLink} from 'components/ExternalLink'; import sidebarBlog from '../../sidebarBlog.json'; import * as React from 'react'; import Image from 'next/image'; function Section({children, background = null}) { return (
    {children}
    ); } function Header({children}) { // "lg:max-w-xl" has been removed in Japanese version return (

    {children}

    ); } function Para({children}) { return (

    {children}

    ); } function Center({children}) { return (
    {children}
    ); } function FullBleed({children}) { return (
    {children}
    ); } function CurrentTime() { const [date, setDate] = useState(new Date()); const currentTime = date.toLocaleTimeString([], { hour: 'numeric', minute: 'numeric', }); useEffect(() => { const msPerMinute = 60 * 1000; let nextMinute = Math.floor(+date / msPerMinute + 1) * msPerMinute; const timeout = setTimeout(() => { if (Date.now() > nextMinute) { setDate(new Date()); } }, nextMinute - Date.now()); return () => clearTimeout(timeout); }, [date]); return {currentTime}; } const blogSidebar = sidebarBlog.routes[1]; if (blogSidebar.path !== '/blog') { throw Error('Could not find the blog route in sidebarBlog.json'); } const recentPosts = blogSidebar.routes.slice(0, 4).map((entry) => ({ title: entry.titleForHomepage, icon: entry.icon, date: entry.date, url: entry.path, })); export function HomeContent() { return ( <>
    logo by @sawaratsuki1004

    React

    Web とネイティブユーザインターフェースのためのライブラリ

    React を学ぶ API リファレンス
    コンポーネントから
    ユーザインターフェースを作成
    React ではユーザインターフェースを、{''} コンポーネントと呼ばれる部品を使って構築できます。 ThumbnailLikeButtonVideo といった React コンポーネントを書き、{''} それらを組み合わせて画面やページやアプリの全体を組み立てましょう。
    独りで開発していても、数千の開発者と共同開発していても、{''} React の開発体験は同じです。個人、チーム、大規模な組織によって{''} 書かれたさまざまなコンポーネントを、シームレスに組み合わせながら {''} 開発できる。それが React の設計理念です。
    マークアップとコードから
    コンポーネントを作成
    React コンポーネントは単なる JavaScript の関数です。{''} 条件によってコンテンツの表示を変えたければ if{' '} 文を使いましょう! リストを表示したいなら配列の map(){' '} を使いましょう! React を学ぶということは、プログラミングを学ぶということなのです。
    このマークアップ構文は JSX と呼ばれます。React が普及させた JavaScript の構文拡張です。JSX マークアップは関連する{''} レンダリングロジックのすぐそばに配置できるので、React コンポーネントは簡単に作成、保守、削除ができます。
    インタラクティブ機能を
    どこでも必要な場所に
    React コンポーネントはデータを受け取り、画面に表示するものを返します。 {''} 入力フィールドへのタイピングなどのユーザ操作によって{''} 新しいデータができたら、コンポーネントにそれを渡します。{''} React が新しいデータに基づいて画面を更新します。
    ページ全体を React で構築する必要はありません。既存の HTML ページに React を追加すれば、どんな場所にでもインタラクティブな React コンポーネントを表示できます。
    既存のページに React を追加する
    フレームワークで
    フルスタックな開発を
    React はライブラリです。{''} コンポーネントを組み合わせることはできますが、{''} ルーティングやデータフェッチの方法までは指定しません。{''} React でアプリ全体を構築する場合は、{''} Next.js や{' '} React Router{' '} のようなフルスタックのフレームワークをお勧めします。
    React とはアーキテクチャでもあります。{''} フレームワークでは、サーバやビルド時に動作する{''} 非同期コンポーネントを使ってデータの取得が可能です。{''} ファイルやデータベースからデータを読み込んで、{''} インタラクティブなコンポーネントに渡しましょう。
    フレームワークで始める
    あらゆるプラットフォームの
    能力を最大限に活用
    人々はウェブを愛し、そしてネイティブアプリを愛しています。{''} その理由は様々です。{''} React を使えば、同じスキルを使ってウェブアプリとネイティブアプリの{''} 両方を構築できます。{''} 各プラットフォームが持つ独自の強みを活かし、{''} どんなプラットフォームにおいても自然なインターフェースを実現します。

    ウェブの本質に忠実に

    人々はウェブアプリが素早く読み込まれることを期待します。 {''} React を使用すれば、サーバ上でデータが取得中でも HTML のストリーミングを開始でき、JavaScript コードが読み込まれる前に{''} コンテンツを段階的にロードすることができます。{''} クライアント側では、React は標準的な Web API を使用して、レンダーの最中でも UI の応答性を保ちます。

    真にネイティブな体験を実現

    人々はネイティブアプリがそのプラットフォームに見合った {''} ルック&フィールを持つことを期待します。 React Native {' '} や{' '} Expo{' '} を使えば、React で Android、iOS などのアプリを構築できます。{''} ネイティブアプリのように感じるのは、{''} ウェブビューではなく真のネイティブ UI だからです。{''} React コンポーネントは、プラットフォーム固有の、{''} 本物の Android や iOS のビューを表示できます。

    React を使えば、{''} ウェブ開発者にもネイティブアプリ開発者にもなれるのです。{''} ユーザー体験を犠牲にすることなく、{''} 多くのプラットフォームでリリースを行えます。{''} ひとつのプラットフォームに縛られることなく、{''} すべての機能をエンドツーエンドで担当するチームを作れます。
    ネイティブプラットフォーム向けに開発する
    完成した機能だけが
    リリースされる
    React は開発アプローチの変更に慎重に取り組みます。{''} すべてのコミットは 10 億人以上のユーザによる{''} ビジネスクリティカルな環境においてテストされます。{''} Meta にある 10 万以上の React コンポーネントが、{''} すべての移行戦略の検証を支援します。
    React チームは、常に React を改善する方法を模索していますが、{''} 研究によっては成果が出るまでに何年もかかることもあります。 {''} 研究のアイデアをリリースするまでの高いハードルを越えた、{''} 実証済みのアプローチだけが React の一部となるのです。
    React のニュースを読む

    最新の React ニュース

    Read more React news
    数百万人の
    コミュニティに参加しよう
    あなたは 1 人ではありません。世界中から毎月 200 万人の開発者が React ドキュメントに訪れています。{''} 人々とチームが共感できる技術、それが React なのです。
    React は単なるライブラリやアーキテクチャ、{''} あるいはエコシステム以上の存在です。{''} React とはコミュニティです。{''} ヘルプを求め、チャンスを見つけ、新しい友人に会える場所です。 {''} 開発者やデザイナ、初心者やエキスパート、{''} 研究者やアーティスト、教師や学生と出会える場所です。{''} 私たちのバックグラウンドはさまざまですが、React を通じて皆で {''} ユーザーインターフェースの創造に取り組んでいるのです。
    logo by @sawaratsuki1004
    React コミュニティに
    ようこそ!
    はじめる
    ); } function CTA({children, icon, href}) { let Tag; let extraProps; if (href.startsWith('https://')) { Tag = ExternalLink; } else { Tag = NextLink; extraProps = {legacyBehavior: false}; } return ( {icon === 'native' && ( )} {icon === 'framework' && ( )} {icon === 'code' && ( )} {icon === 'news' && ( )} {children} ); } const reactConf2021Cover = '/images/home/conf2021/cover.svg'; const reactConf2019Cover = '/images/home/conf2019/cover.svg'; const communityImages = [ { src: '/images/home/community/react_conf_fun.webp', alt: 'People singing karaoke at React Conf', }, { src: '/images/home/community/react_india_sunil.webp', alt: 'Sunil Pai speaking at React India', }, { src: '/images/home/community/react_conf_hallway.webp', alt: 'A hallway conversation between two people at React Conf', }, { src: '/images/home/community/react_india_hallway.webp', alt: 'A hallway conversation at React India', }, { src: '/images/home/community/react_conf_elizabet.webp', alt: 'Elizabet Oliveira speaking at React Conf', }, { src: '/images/home/community/react_india_selfie.webp', alt: 'People taking a group selfie at React India', }, { src: '/images/home/community/react_conf_nat.webp', alt: 'Nat Alison speaking at React Conf', }, { src: '/images/home/community/react_india_team.webp', alt: 'Organizers greeting attendees at React India', }, ]; function CommunityGallery() { const ref = useRef(); const [shouldPlay, setShouldPlay] = useState(false); useEffect(() => { const observer = new IntersectionObserver( (entries) => { entries.forEach((entry) => { setShouldPlay(entry.isIntersecting); }); }, { root: null, rootMargin: `${window.innerHeight}px 0px`, } ); observer.observe(ref.current); return () => observer.disconnect(); }, []); const [isLazy, setIsLazy] = useState(true); // Either wait until we're scrolling close... useEffect(() => { if (!isLazy) { return; } const rootVertical = parseInt(window.innerHeight * 2.5); const observer = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { setIsLazy(false); } }); }, { root: null, rootMargin: `${rootVertical}px 0px`, } ); observer.observe(ref.current); return () => observer.disconnect(); }, [isLazy]); // ... or until it's been a while after hydration. useEffect(() => { const timeout = setTimeout(() => { setIsLazy(false); }, 20 * 1000); return () => clearTimeout(timeout); }, []); return (
    ); } const CommunityImages = memo(function CommunityImages({isLazy}) { return ( <> {communityImages.map(({src, alt}, i) => (
    {alt}
    ))} ); }); function ExampleLayout({ filename, left, right, activeArea, hoverTopOffset = 0, }) { const contentRef = useRef(null); useNestedScrollLock(contentRef); const [overlayStyles, setOverlayStyles] = useState([]); useEffect(() => { if (activeArea) { const nodes = contentRef.current.querySelectorAll( '[data-hover="' + activeArea.name + '"]' ); const nextOverlayStyles = Array.from(nodes) .map((node) => { const parentRect = contentRef.current.getBoundingClientRect(); const nodeRect = node.getBoundingClientRect(); let top = Math.round(nodeRect.top - parentRect.top) - 8; let bottom = Math.round(nodeRect.bottom - parentRect.top) + 8; let left = Math.round(nodeRect.left - parentRect.left) - 8; let right = Math.round(nodeRect.right - parentRect.left) + 8; top = Math.max(top, hoverTopOffset); bottom = Math.min(bottom, parentRect.height - 12); if (top >= bottom) { return null; } return { width: right - left + 'px', height: bottom - top + 'px', transform: `translate(${left}px, ${top}px)`, }; }) .filter((s) => s !== null); setOverlayStyles(nextOverlayStyles); } }, [activeArea, hoverTopOffset]); return (

    {filename}

    {left}
    {right}
    {overlayStyles.map((styles, i) => (
    ))}
    ); } function useCodeHover(areas) { const [hoverLine, setHoverLine] = useState(null); const area = areas.get(hoverLine); let meta; if (area) { const highlightLines = area.lines ?? [hoverLine]; meta = '```js {' + highlightLines.map((l) => l + 1).join(',') + '}'; } return [area, meta, setHoverLine]; } const example1Areas = new Map([ [2, {name: 'Video'}], [3, {name: 'Thumbnail'}], [4, {name: 'a'}], [5, {name: 'h3'}], [6, {name: 'p'}], [7, {name: 'a'}], [8, {name: 'LikeButton'}], [9, {name: 'Video'}], ]); function Example1() { const [area, meta, onLineHover] = useCodeHover(example1Areas); return (
    {`function Video({ video }) { return ( ); } `}
    } right={ } /> ); } const example2Areas = new Map([ [8, {name: 'VideoList'}], [9, {name: 'h2'}], [11, {name: 'Video', lines: [11]}], [13, {name: 'VideoList'}], ]); function Example2() { const [area, meta, onLineHover] = useCodeHover(example2Areas); const videos = [ { id: 'ex2-0', title: 'First video', description: 'Video description', image: 'blue', }, { id: 'ex2-1', title: 'Second video', description: 'Video description', image: 'red', }, { id: 'ex2-2', title: 'Third video', description: 'Video description', image: 'green', }, ]; return (
    {`function VideoList({ videos, emptyHeading }) { const count = videos.length; let heading = emptyHeading; if (count > 0) { const noun = count > 1 ? 'Videos' : 'Video'; heading = count + ' ' + noun; } return (

    {heading}

    {videos.map(video =>
    ); }`}
    } right={
    } /> ); } const example3Areas = new Map([ [6, {name: 'SearchableVideoList'}], [7, {name: 'SearchInput', lines: [7, 8, 9]}], [8, {name: 'SearchInput', lines: [7, 8, 9]}], [9, {name: 'SearchInput', lines: [7, 8, 9]}], [10, {name: 'VideoList', lines: [10, 11, 12]}], [11, {name: 'VideoList', lines: [10, 11, 12]}], [12, {name: 'VideoList', lines: [10, 11, 12]}], [13, {name: 'SearchableVideoList'}], ]); function Example3() { const [area, meta, onLineHover] = useCodeHover(example3Areas); const videos = [ { id: 'vids-0', title: 'React: The Documentary', description: 'The origin story of React', image: '/images/home/videos/documentary.webp', url: 'https://www.youtube.com/watch?v=8pDqJVdNa44', }, { id: 'vids-1', title: 'Rethinking Best Practices', description: 'Pete Hunt (2013)', image: '/images/home/videos/rethinking.jpg', url: 'https://www.youtube.com/watch?v=x7cQ3mrcKaY', }, { id: 'vids-2', title: 'Introducing React Native', description: 'Tom Occhino (2015)', image: '/images/home/videos/rn.jpg', url: 'https://www.youtube.com/watch?v=KVZ-P-ZI6W4', }, { id: 'vids-3', title: 'Introducing React Hooks', description: 'Sophie Alpert and Dan Abramov (2018)', image: '/images/home/videos/hooks.jpg', url: 'https://www.youtube.com/watch?v=V-QO-KO90iQ', }, { id: 'vids-4', title: 'Introducing Server Components', description: 'Dan Abramov and Lauren Tan (2020)', image: '/images/home/videos/rsc.jpg', url: 'https://www.youtube.com/watch?v=TQQPAU21ZUw', }, ]; return (
    {`import { useState } from 'react'; function SearchableVideoList({ videos }) { const [searchText, setSearchText] = useState(''); const foundVideos = filterVideos(videos, searchText); return ( <> setSearchText(newText)} /> ); }`}
    } right={

    React Videos

    A brief history of React

    } /> ); } const example4Areas = new Map([ [6, {name: 'ConferenceLayout'}], [7, {name: 'Suspense'}], [8, {name: 'SearchableVideoList'}], [9, {name: 'Suspense'}], [10, {name: 'ConferenceLayout'}], [17, {name: 'SearchableVideoList'}], ]); function Example4() { const [area, meta, onLineHover] = useCodeHover(example4Areas); const [slug, setSlug] = useState('react-conf-2021'); const [animate, setAnimate] = useState(false); function navigate(newSlug) { setSlug(newSlug); setAnimate(true); } return (
    {`import { db } from './database.js'; import { Suspense } from 'react'; async function ConferencePage({ slug }) { const conf = await db.Confs.find({ slug }); return ( }> ); } async function Talks({ confId }) { const talks = await db.Talks.findAll({ confId }); const videos = talks.map(talk => talk.video); return ; }`}
    } right={
    } /> ); } function useNestedScrollLock(ref) { useEffect(() => { let node = ref.current; let isLocked = false; let lastScroll = performance.now(); function handleScroll() { if (!isLocked) { isLocked = true; node.style.pointerEvents = 'none'; } lastScroll = performance.now(); } function updateLock() { if (isLocked && performance.now() - lastScroll > 150) { isLocked = false; node.style.pointerEvents = ''; } } window.addEventListener('scroll', handleScroll); const interval = setInterval(updateLock, 60); return () => { window.removeEventListener('scroll', handleScroll); clearInterval(interval); }; }, [ref]); } function ExamplePanel({ children, noPadding, noShadow, height, contentMarginTop, }) { return (
    {children}
    ); } const NavContext = createContext(null); function BrowserChrome({children, hasPulse, hasRefresh, domain, path}) { const [restartId, setRestartId] = useState(0); const isPulsing = hasPulse && restartId === 0; const [shouldAnimatePulse, setShouldAnimatePulse] = useState(false); const refreshRef = useRef(null); useEffect(() => { if (!isPulsing) { return; } const observer = new IntersectionObserver( (entries) => { entries.forEach((entry) => { setShouldAnimatePulse(entry.isIntersecting); }); }, { root: null, rootMargin: `0px 0px`, } ); observer.observe(refreshRef.current); return () => observer.disconnect(); }, [isPulsing]); function handleRestart() { confCache = new Map(); talksCache = new Map(); setRestartId((i) => i + 1); } return (
    {hasRefresh &&
    }
    {domain} {path != null && '/'} {path}
    {hasRefresh && (
    {isPulsing && shouldAnimatePulse && (
    )}
    )}
    {restartId > 0 && (
    )}
    {children}
    ); } function ConferencePage({slug}) { const conf = use(fetchConf(slug)); return (
    }>
    ); } function TalksLoading() { return (
    ); } function Talks({confId}) { const videos = use(fetchTalks(confId)); return ; } function SearchableVideoList({videos}) { const [searchText, setSearchText] = useState(''); const foundVideos = filterVideos(videos, searchText); return (
    ); } function filterVideos(videos, query) { const keywords = query .toLowerCase() .split(' ') .filter((s) => s !== ''); if (keywords.length === 0) { return videos; } return videos.filter((video) => { const words = (video.title + ' ' + video.description) .toLowerCase() .split(' '); return keywords.every((kw) => words.some((w) => w.startsWith(kw))); }); } function VideoList({videos, emptyHeading}) { let heading = emptyHeading; const count = videos.length; if (count > 0) { const noun = count > 1 ? 'Videos' : 'Video'; heading = count + ' ' + noun; } return (

    {heading}

    {videos.map((video) => (
    ); } function SearchInput({value, onChange}) { const id = useId(); return ( e.preventDefault()}>
    onChange(e.target.value)} />
    ); } function ConferenceLayout({conf, children}) { const {slug, navigate} = useContext(NavContext); const [isPending, startTransition] = useTransition(); return (
    {children}
    ); } function Cover({background, children}) { return (
    {children}
    ); } function Video({video}) { return ( ); } function Code({children}) { return ( {children} ); } function Thumbnail({video}) { const {image} = video; return ( ); } function ThumbnailPlaceholder() { return ( ); } // A hack since we don't actually have a backend. // Unlike local state, this survives videos being filtered. const likedVideos = new Set(); function LikeButton({video}) { const [isLiked, setIsLiked] = useState(() => likedVideos.has(video.id)); const [animate, setAnimate] = useState(false); return ( ); } function SvgContainer({children}) { return ( {children} ); } function NativeIcons() { return (
    ); } function WebIcons() { return (
    ); } // TODO: upgrade React and use the built-in version. function use(promise) { if (promise.status === 'fulfilled') { return promise.value; } else if (promise.status === 'rejected') { throw promise.reason; } else if (promise.status === 'pending') { throw promise; } else { promise.status = 'pending'; promise.then( (result) => { promise.status = 'fulfilled'; promise.value = result; }, (reason) => { promise.status = 'rejected'; promise.reason = reason; } ); throw promise; } } let confCache = new Map(); let talksCache = new Map(); const loadConfDelay = 250; const loadTalksDelay = 1000; function fetchConf(slug) { if (confCache.has(slug)) { return confCache.get(slug); } const promise = new Promise((resolve) => { setTimeout(() => { if (slug === 'react-conf-2021') { resolve({ id: 0, cover: reactConf2021Cover, name: 'React Conf 2021', }); } else if (slug === 'react-conf-2019') { resolve({ id: 1, cover: reactConf2019Cover, name: 'React Conf 2019', }); } }, loadConfDelay); }); confCache.set(slug, promise); return promise; } function fetchTalks(confId) { if (talksCache.has(confId)) { return talksCache.get(confId); } const promise = new Promise((resolve) => { setTimeout(() => { if (confId === 0) { resolve([ { id: 'conf-2021-0', title: 'React 18 Keynote', description: 'The React Team', url: 'https://www.youtube.com/watch?v=FZ0cG47msEk&list=PLNG_1j3cPCaZZ7etkzWA7JfdmKWT0pMsa&index=1', image: { speakers: [ '/images/home/conf2021/andrew.jpg', '/images/home/conf2021/lauren.jpg', '/images/home/conf2021/juan.jpg', '/images/home/conf2021/rick.jpg', ], }, }, { id: 'conf-2021-1', title: 'React 18 for App Developers', description: 'Shruti Kapoor', url: 'https://www.youtube.com/watch?v=ytudH8je5ko&list=PLNG_1j3cPCaZZ7etkzWA7JfdmKWT0pMsa&index=2', image: { speakers: ['/images/home/conf2021/shruti.jpg'], }, }, { id: 'conf-2021-2', title: 'Streaming Server Rendering with Suspense', description: 'Shaundai Person', url: 'https://www.youtube.com/watch?v=pj5N-Khihgc&list=PLNG_1j3cPCaZZ7etkzWA7JfdmKWT0pMsa&index=3', image: { speakers: ['/images/home/conf2021/shaundai.jpg'], }, }, { id: 'conf-2021-3', title: 'The First React Working Group', description: 'Aakansha Doshi', url: 'https://www.youtube.com/watch?v=qn7gRClrC9U&list=PLNG_1j3cPCaZZ7etkzWA7JfdmKWT0pMsa&index=4', image: { speakers: ['/images/home/conf2021/aakansha.jpg'], }, }, { id: 'conf-2021-4', title: 'React Developer Tooling', description: 'Brian Vaughn', url: 'https://www.youtube.com/watch?v=oxDfrke8rZg&list=PLNG_1j3cPCaZZ7etkzWA7JfdmKWT0pMsa&index=5', image: { speakers: ['/images/home/conf2021/brian.jpg'], }, }, { id: 'conf-2021-5', title: 'React without memo', description: 'Xuan Huang (黄玄)', url: 'https://www.youtube.com/watch?v=lGEMwh32soc&list=PLNG_1j3cPCaZZ7etkzWA7JfdmKWT0pMsa&index=6', image: { speakers: ['/images/home/conf2021/xuan.jpg'], }, }, { id: 'conf-2021-6', title: 'React Docs Keynote', description: 'Rachel Nabors', url: 'https://www.youtube.com/watch?v=mneDaMYOKP8&list=PLNG_1j3cPCaZZ7etkzWA7JfdmKWT0pMsa&index=7', image: { speakers: ['/images/home/conf2021/rachel.jpg'], }, }, { id: 'conf-2021-7', title: 'Things I Learnt from the New React Docs', description: "Debbie O'Brien", url: 'https://www.youtube.com/watch?v=-7odLW_hG7s&list=PLNG_1j3cPCaZZ7etkzWA7JfdmKWT0pMsa&index=8', image: { speakers: ['/images/home/conf2021/debbie.jpg'], }, }, { id: 'conf-2021-8', title: 'Learning in the Browser', description: 'Sarah Rainsberger', url: 'https://www.youtube.com/watch?v=5X-WEQflCL0&list=PLNG_1j3cPCaZZ7etkzWA7JfdmKWT0pMsa&index=9', image: { speakers: ['/images/home/conf2021/sarah.jpg'], }, }, { id: 'conf-2021-9', title: 'The ROI of Designing with React', description: 'Linton Ye', url: 'https://www.youtube.com/watch?v=7cPWmID5XAk&list=PLNG_1j3cPCaZZ7etkzWA7JfdmKWT0pMsa&index=10', image: { speakers: ['/images/home/conf2021/linton.jpg'], }, }, { id: 'conf-2021-10', title: 'Interactive Playgrounds with React', description: 'Delba de Oliveira', url: 'https://www.youtube.com/watch?v=zL8cz2W0z34&list=PLNG_1j3cPCaZZ7etkzWA7JfdmKWT0pMsa&index=11', image: { speakers: ['/images/home/conf2021/delba.jpg'], }, }, { id: 'conf-2021-11', title: 'Re-introducing Relay', description: 'Robert Balicki', url: 'https://www.youtube.com/watch?v=lhVGdErZuN4&list=PLNG_1j3cPCaZZ7etkzWA7JfdmKWT0pMsa&index=12', image: { speakers: ['/images/home/conf2021/robert.jpg'], }, }, { id: 'conf-2021-12', title: 'React Native Desktop', description: 'Eric Rozell and Steven Moyes', url: 'https://www.youtube.com/watch?v=9L4FFrvwJwY&list=PLNG_1j3cPCaZZ7etkzWA7JfdmKWT0pMsa&index=13', image: { speakers: [ '/images/home/conf2021/eric.jpg', '/images/home/conf2021/steven.jpg', ], }, }, { id: 'conf-2021-13', title: 'On-device Machine Learning for React Native', description: 'Roman Rädle', url: 'https://www.youtube.com/watch?v=NLj73vrc2I8&list=PLNG_1j3cPCaZZ7etkzWA7JfdmKWT0pMsa&index=14', image: { speakers: ['/images/home/conf2021/roman.jpg'], }, }, { id: 'conf-2021-14', title: 'React 18 for External Store Libraries', description: 'Daishi Kato', url: 'https://www.youtube.com/watch?v=oPfSC5bQPR8&list=PLNG_1j3cPCaZZ7etkzWA7JfdmKWT0pMsa&index=15', image: { speakers: ['/images/home/conf2021/daishi.jpg'], }, }, { id: 'conf-2021-15', title: 'Building Accessible Components with React 18', description: 'Diego Haz', url: 'https://www.youtube.com/watch?v=dcm8fjBfro8&list=PLNG_1j3cPCaZZ7etkzWA7JfdmKWT0pMsa&index=16', image: { speakers: ['/images/home/conf2021/diego.jpg'], }, }, { id: 'conf-2021-16', title: 'Accessible Japanese Form Components with React', description: 'Tafu Nakazaki', url: 'https://www.youtube.com/watch?v=S4a0QlsH0pU&list=PLNG_1j3cPCaZZ7etkzWA7JfdmKWT0pMsa&index=17', image: { speakers: ['/images/home/conf2021/tafu.jpg'], }, }, { id: 'conf-2021-17', title: 'UI Tools for Artists', description: 'Lyle Troxell', url: 'https://www.youtube.com/watch?v=b3l4WxipFsE&list=PLNG_1j3cPCaZZ7etkzWA7JfdmKWT0pMsa&index=18', image: { speakers: ['/images/home/conf2021/lyle.jpg'], }, }, { id: 'conf-2021-18', title: 'Hydrogen + React 18', description: 'Helen Lin', url: 'https://www.youtube.com/watch?v=HS6vIYkSNks&list=PLNG_1j3cPCaZZ7etkzWA7JfdmKWT0pMsa&index=19', image: { speakers: ['/images/home/conf2021/helen.jpg'], }, }, ]); } else if (confId === 1) { resolve([ { id: 'conf-2019-0', title: 'Keynote (Part 1)', description: 'Tom Occhino', url: 'https://www.youtube.com/watch?v=QnZHO7QvjaM&list=PLPxbbTqCLbGHPxZpw4xj_Wwg8-fdNxJRh', image: { speakers: ['/images/home/conf2019/tom.jpg'], }, }, { id: 'conf-2019-1', title: 'Keynote (Part 2)', description: 'Yuzhi Zheng', url: 'https://www.youtube.com/watch?v=uXEEL9mrkAQ&list=PLPxbbTqCLbGHPxZpw4xj_Wwg8-fdNxJRh&index=2', image: { speakers: ['https://conf2019.reactjs.org/img/speakers/yuzhi.jpg'], }, }, { id: 'conf-2019-2', title: 'Building The New Facebook With React and Relay (Part 1)', description: 'Frank Yan', url: 'https://www.youtube.com/watch?v=9JZHodNR184&list=PLPxbbTqCLbGHPxZpw4xj_Wwg8-fdNxJRh&index=3', image: { speakers: ['/images/home/conf2019/frank.jpg'], }, }, { id: 'conf-2019-3', title: 'Building The New Facebook With React and Relay (Part 2)', description: 'Ashley Watkins', url: 'https://www.youtube.com/watch?v=KT3XKDBZW7M&list=PLPxbbTqCLbGHPxZpw4xj_Wwg8-fdNxJRh&index=4', image: { speakers: ['/images/home/conf2019/ashley.jpg'], }, }, { id: 'conf-2019-4', title: 'How Our Team Is Using React Native to Save The World', description: 'Tania Papazafeiropoulou', url: 'https://www.youtube.com/watch?v=zVHWugBPGBE&list=PLPxbbTqCLbGHPxZpw4xj_Wwg8-fdNxJRh&index=5', image: { speakers: ['/images/home/conf2019/tania.jpg'], }, }, { id: 'conf-2019-5', title: 'Using Hooks and Codegen to Bring the Benefits of GraphQL to REST APIs', description: 'Tejas Kumar', url: 'https://www.youtube.com/watch?v=cdsnzfJUqm0&list=PLPxbbTqCLbGHPxZpw4xj_Wwg8-fdNxJRh&index=6', image: { speakers: ['/images/home/conf2019/tejas.jpg'], }, }, { id: 'conf-2019-6', title: 'Building a Custom React Renderer', description: 'Sophie Alpert', url: 'https://www.youtube.com/watch?v=CGpMlWVcHok&list=PLPxbbTqCLbGHPxZpw4xj_Wwg8-fdNxJRh&index=7', image: { speakers: ['/images/home/conf2019/sophie.jpg'], }, }, { id: 'conf-2019-7', title: 'Is React Translated Yet?', description: 'Nat Alison', url: 'https://www.youtube.com/watch?v=lLE4Jqaek5k&list=PLPxbbTqCLbGHPxZpw4xj_Wwg8-fdNxJRh&index=12', image: { speakers: ['/images/home/conf2019/nat.jpg'], }, }, { id: 'conf-2019-8', title: 'Building (And Re-Building) the Airbnb Design System', description: 'Maja Wichrowska and Tae Kim', url: 'https://www.youtube.com/watch?v=fHQ1WSx41CA&list=PLPxbbTqCLbGHPxZpw4xj_Wwg8-fdNxJRh&index=13', image: { speakers: [ '/images/home/conf2019/maja.jpg', '/images/home/conf2019/tae.jpg', ], }, }, { id: 'conf-2019-9', title: 'Accessibility Is a Marathon, Not a Sprint', description: 'Brittany Feenstra', url: 'https://www.youtube.com/watch?v=ONSD-t4gBb8&list=PLPxbbTqCLbGHPxZpw4xj_Wwg8-fdNxJRh&index=14', image: { speakers: ['/images/home/conf2019/brittany.jpg'], }, }, { id: 'conf-2019-10', title: 'The State of React State in 2019', description: 'Becca Bailey', url: 'https://www.youtube.com/watch?v=wUMMUyQtMSg&list=PLPxbbTqCLbGHPxZpw4xj_Wwg8-fdNxJRh&index=15', image: { speakers: ['/images/home/conf2019/becca.jpg'], }, }, { id: 'conf-2019-11', title: 'Let’s Program Like It’s 1999', description: 'Lee Byron', url: 'https://www.youtube.com/watch?v=vG8WpLr6y_U&list=PLPxbbTqCLbGHPxZpw4xj_Wwg8-fdNxJRh&index=16', image: { speakers: ['/images/home/conf2019/lee.jpg'], }, }, { id: 'conf-2019-12', title: 'React Developer Tooling', description: 'Brian Vaughn', url: 'https://www.youtube.com/watch?v=Mjrfb1r3XEM&list=PLPxbbTqCLbGHPxZpw4xj_Wwg8-fdNxJRh&index=17', image: { speakers: ['/images/home/conf2019/brian.jpg'], }, }, { id: 'conf-2019-13', title: 'Data Fetching With Suspense In Relay', description: 'Joe Savona', url: 'https://www.youtube.com/watch?v=Tl0S7QkxFE4&list=PLPxbbTqCLbGHPxZpw4xj_Wwg8-fdNxJRh&index=18', image: { speakers: ['/images/home/conf2019/joe.jpg'], }, }, { id: 'conf-2019-14', title: 'Automatic Visualizations of the Frontend', description: 'Cameron Yick', url: 'https://www.youtube.com/watch?v=SbreAPNmZOk&list=PLPxbbTqCLbGHPxZpw4xj_Wwg8-fdNxJRh&index=19', image: { speakers: ['/images/home/conf2019/cameron.jpg'], }, }, { id: 'conf-2019-15', title: 'React Is Fiction', description: 'Jenn Creighton', url: 'https://www.youtube.com/watch?v=kqh4lz2Lkzs&list=PLPxbbTqCLbGHPxZpw4xj_Wwg8-fdNxJRh&index=20', image: { speakers: ['/images/home/conf2019/jenn.jpg'], }, }, { id: 'conf-2019-16', title: 'Progressive Web Animations', description: 'Alexandra Holachek', url: 'https://www.youtube.com/watch?v=laPsceJ4tTY&list=PLPxbbTqCLbGHPxZpw4xj_Wwg8-fdNxJRh&index=21', image: { speakers: ['/images/home/conf2019/alexandra.jpg'], }, }, { id: 'conf-2019-17', title: 'Creating Games, Animations and Interactions with the Wick Editor', description: 'Luca Damasco', url: 'https://www.youtube.com/watch?v=laPsceJ4tTY&list=PLPxbbTqCLbGHPxZpw4xj_Wwg8-fdNxJRh&index=21', image: { speakers: ['/images/home/conf2019/luca.jpg'], }, }, { id: 'conf-2019-18', title: 'Building React-Select', description: 'Jed Watson', url: 'https://www.youtube.com/watch?v=yS0jUnmBujE&list=PLPxbbTqCLbGHPxZpw4xj_Wwg8-fdNxJRh&index=25', image: { speakers: ['/images/home/conf2019/jed.jpg'], }, }, { id: 'conf-2019-19', title: 'Promoting Transparency in Government Spending with React', description: 'Lizzie Salita', url: 'https://www.youtube.com/watch?v=CVfXICcNfHE&list=PLPxbbTqCLbGHPxZpw4xj_Wwg8-fdNxJRh&index=26', image: { speakers: ['/images/home/conf2019/lizzie.jpg'], }, }, { id: 'conf-2019-20', title: 'Wonder-driven Development: Using React to Make a Spaceship', description: 'Alex Anderson', url: 'https://www.youtube.com/watch?v=aV0uOPWHKt4&list=PLPxbbTqCLbGHPxZpw4xj_Wwg8-fdNxJRh&index=27', image: { speakers: ['/images/home/conf2019/alex.jpg'], }, }, ]); } }, loadTalksDelay); }); talksCache.set(confId, promise); return promise; } ================================================ FILE: src/components/Layout/Page.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {Suspense} from 'react'; import * as React from 'react'; import {useRouter} from 'next/router'; import {SidebarNav} from './SidebarNav'; import {Footer} from './Footer'; import {Toc} from './Toc'; // import SocialBanner from '../SocialBanner'; import {DocsPageFooter} from 'components/DocsFooter'; import {Seo} from 'components/Seo'; import PageHeading from 'components/PageHeading'; import {getRouteMeta} from './getRouteMeta'; import {TocContext} from '../MDX/TocContext'; import {Languages, LanguagesContext} from '../MDX/LanguagesContext'; import type {TocItem} from 'components/MDX/TocContext'; import type {RouteItem} from 'components/Layout/getRouteMeta'; import {HomeContent} from './HomeContent'; import {TopNav} from './TopNav'; import cn from 'classnames'; import Head from 'next/head'; import(/* webpackPrefetch: true */ '../MDX/CodeBlock/CodeBlock'); interface PageProps { children: React.ReactNode; toc: Array; routeTree: RouteItem; meta: { title?: string; titleForTitleTag?: string; version?: 'experimental' | 'canary'; description?: string; }; section: 'learn' | 'reference' | 'community' | 'blog' | 'home' | 'unknown'; languages?: Languages | null; } export function Page({ children, toc, routeTree, meta, section, languages = null, }: PageProps) { const {asPath} = useRouter(); const cleanedPath = asPath.split(/[\?\#]/)[0]; const {route, nextRoute, prevRoute, breadcrumbs, order} = getRouteMeta( cleanedPath, routeTree ); const title = meta.title || route?.title || ''; const version = meta.version; const description = meta.description || route?.description || ''; const isHomePage = cleanedPath === '/'; const isBlogIndex = cleanedPath === '/blog'; let content; if (isHomePage) { content = ; } else { content = (
    {children}
    {!isBlogIndex && ( )}
    ); } let hasColumns = true; let showSidebar = true; let showToc = true; if (isHomePage || isBlogIndex) { hasColumns = false; showSidebar = false; showToc = false; } else if (section === 'blog') { showToc = false; hasColumns = false; showSidebar = false; } let searchOrder; if (section === 'learn' || (section === 'blog' && !isBlogIndex)) { searchOrder = order; } return ( <> {(isHomePage || isBlogIndex) && ( )} {/* */}
    {showSidebar && (
    )} {/* No fallback UI so need to be careful not to suspend directly inside. */}
    {content}
    {!isHomePage && (

    )}
    {showToc && toc.length > 0 && }
    ); } ================================================ FILE: src/components/Layout/Sidebar/SidebarButton.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import * as React from 'react'; import cn from 'classnames'; import {IconNavArrow} from 'components/Icon/IconNavArrow'; interface SidebarButtonProps { title: string; heading: boolean; level: number; onClick: (event: React.MouseEvent) => void; isExpanded?: boolean; isBreadcrumb?: boolean; } export function SidebarButton({ title, heading, level, onClick, isExpanded, isBreadcrumb, }: SidebarButtonProps) { return (
    1, })}>
    ); } ================================================ FILE: src/components/Layout/Sidebar/SidebarLink.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ /* eslint-disable jsx-a11y/no-static-element-interactions */ /* eslint-disable jsx-a11y/click-events-have-key-events */ import {useRef, useEffect} from 'react'; import * as React from 'react'; import cn from 'classnames'; import {IconNavArrow} from 'components/Icon/IconNavArrow'; import {IconCanary} from 'components/Icon/IconCanary'; import {IconExperimental} from 'components/Icon/IconExperimental'; import Link from 'next/link'; interface SidebarLinkProps { href: string; selected?: boolean; title: string; level: number; version?: 'canary' | 'major' | 'experimental' | 'rc'; icon?: React.ReactNode; isExpanded?: boolean; hideArrow?: boolean; isPending: boolean; } export function SidebarLink({ href, selected = false, title, version, level, isExpanded, hideArrow, isPending, }: SidebarLinkProps) { const ref = useRef(null); useEffect(() => { if (selected && ref && ref.current) { // @ts-ignore if (typeof ref.current.scrollIntoViewIfNeeded === 'function') { // @ts-ignore ref.current.scrollIntoViewIfNeeded(); } } }, [ref, selected]); let target = ''; if (href.startsWith('https://')) { target = '_blank'; } return ( 0, 'ps-5': level < 2, 'text-base font-bold': level === 0, 'text-primary dark:text-primary-dark': level === 0 && !selected, 'text-base text-secondary dark:text-secondary-dark': level > 0 && !selected, 'text-base text-link dark:text-link-dark bg-highlight dark:bg-highlight-dark border-blue-40 hover:bg-highlight hover:text-link dark:hover:bg-highlight-dark dark:hover:text-link-dark': selected, 'dark:bg-gray-70 bg-gray-3 dark:hover:bg-gray-70 hover:bg-gray-3': isPending, } )}> {/* This here needs to be refactored ofc */}
    {title}{' '} {version === 'major' && ( React 19 )} {version === 'canary' && ( )} {version === 'experimental' && ( )} {version === 'rc' && ( )}
    {isExpanded != null && !hideArrow && ( )} ); } ================================================ FILE: src/components/Layout/Sidebar/SidebarRouteTree.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {useRef, useLayoutEffect, Fragment} from 'react'; import cn from 'classnames'; import {useRouter} from 'next/router'; import {SidebarLink} from './SidebarLink'; import {useCollapse} from 'react-collapsed'; import usePendingRoute from 'hooks/usePendingRoute'; import type {RouteItem} from 'components/Layout/getRouteMeta'; import {siteConfig} from 'siteConfig'; interface SidebarRouteTreeProps { isForceExpanded: boolean; breadcrumbs: RouteItem[]; routeTree: RouteItem; level?: number; } function CollapseWrapper({ isExpanded, duration, children, }: { isExpanded: boolean; duration: number; children: any; }) { const ref = useRef(null); const timeoutRef = useRef(null); const {getCollapseProps} = useCollapse({ isExpanded, duration, }); // Disable pointer events while animating. const isExpandedRef = useRef(isExpanded); if (typeof window !== 'undefined') { // eslint-disable-next-line react-compiler/react-compiler // eslint-disable-next-line react-hooks/rules-of-hooks useLayoutEffect(() => { const wasExpanded = isExpandedRef.current; if (wasExpanded === isExpanded) { return; } isExpandedRef.current = isExpanded; if (ref.current !== null) { const node: HTMLDivElement = ref.current; node.style.pointerEvents = 'none'; if (timeoutRef.current !== null) { window.clearTimeout(timeoutRef.current); } timeoutRef.current = window.setTimeout(() => { node.style.pointerEvents = ''; }, duration + 100); } }); } return (
    {children}
    ); } export function SidebarRouteTree({ isForceExpanded, breadcrumbs, routeTree, level = 0, }: SidebarRouteTreeProps) { const slug = useRouter().asPath.split(/[\?\#]/)[0]; const pendingRoute = usePendingRoute(); const currentRoutes = routeTree.routes as RouteItem[]; return (
      {currentRoutes.map( ( { path, title, routes, version, heading, hasSectionHeader, sectionHeader, }, index ) => { const selected = slug === path; let listItem = null; if (!path || heading) { // if current route item has no path and children treat it as an API sidebar heading listItem = ( ); } else if (routes) { // if route has a path and child routes, treat it as an expandable sidebar item const isBreadcrumb = breadcrumbs.length > 1 && breadcrumbs[breadcrumbs.length - 1].path === path; const isExpanded = isForceExpanded || isBreadcrumb || selected; listItem = (
    • ); } else { // if route has a path and no child routes, treat it as a sidebar link listItem = (
    • ); } if (hasSectionHeader) { let sectionHeaderText = sectionHeader != null ? sectionHeader.replace('{{version}}', siteConfig.version) : ''; return ( {index !== 0 && (
    • )}

      {sectionHeaderText}

      ); } else { return listItem; } } )}
    ); } ================================================ FILE: src/components/Layout/Sidebar/index.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ export {SidebarButton} from './SidebarButton'; export {SidebarLink} from './SidebarLink'; export {SidebarRouteTree} from './SidebarRouteTree'; ================================================ FILE: src/components/Layout/SidebarNav/SidebarNav.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {Suspense} from 'react'; import * as React from 'react'; import cn from 'classnames'; import {SidebarRouteTree} from '../Sidebar/SidebarRouteTree'; import type {RouteItem} from '../getRouteMeta'; declare global { interface Window { __theme: string; __setPreferredTheme: (theme: string) => void; } } export default function SidebarNav({ routeTree, breadcrumbs, }: { routeTree: RouteItem; breadcrumbs: RouteItem[]; }) { // HACK. Fix up the data structures instead. if ((routeTree as any).routes.length === 1) { routeTree = (routeTree as any).routes[0]; } return (
    ); } ================================================ FILE: src/components/Layout/SidebarNav/index.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ export {default as SidebarNav} from './SidebarNav'; ================================================ FILE: src/components/Layout/Toc.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import cx from 'classnames'; import {useTocHighlight} from './useTocHighlight'; import type {Toc} from '../MDX/TocContext'; export function Toc({headings}: {headings: Toc}) { const {currentIndex} = useTocHighlight(); // TODO: We currently have a mismatch between the headings in the document // and the headings we find in MarkdownPage (i.e. we don't find Recap or Challenges). // Select the max TOC item we have here for now, but remove this after the fix. const selectedIndex = Math.min(currentIndex, headings.length - 1); return ( ); } ================================================ FILE: src/components/Layout/TopNav/BrandMenu.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ import * as ContextMenu from '@radix-ui/react-context-menu'; import {IconCopy} from 'components/Icon/IconCopy'; import {IconDownload} from 'components/Icon/IconDownload'; import {IconNewPage} from 'components/Icon/IconNewPage'; import {ExternalLink} from 'components/ExternalLink'; import {IconClose} from '../../Icon/IconClose'; function MenuItem({ children, onSelect, }: { children: React.ReactNode; onSelect?: () => void; }) { return ( {children} ); } function DownloadMenuItem({ fileName, href, children, }: { fileName: string; href: string; children: React.ReactNode; }) { return ( {children} ); } export default function BrandMenu({children}: {children: React.ReactNode}) { return ( {children} Dark Mode Logo SVG Wordmark SVG { await navigator.clipboard.writeText('#58C4DC'); }}> Copy dark mode color Light Mode Logo SVG Wordmark SVG { await navigator.clipboard.writeText('#087EA4'); }}> Copy light mode color
    uwu { // @ts-ignore window.__setUwu(false); }}> Turn off Logo PNG Logo by @sawaratsuki1004
    ); } ================================================ FILE: src/components/Layout/TopNav/TopNav.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import { useState, useRef, useCallback, useEffect, startTransition, Suspense, } from 'react'; import Image from 'next/image'; import * as React from 'react'; import cn from 'classnames'; import NextLink from 'next/link'; import {useRouter} from 'next/router'; import {disableBodyScroll, enableBodyScroll} from 'body-scroll-lock'; import {IconClose} from 'components/Icon/IconClose'; import {IconHamburger} from 'components/Icon/IconHamburger'; import {IconSearch} from 'components/Icon/IconSearch'; import {Search} from 'components/Search'; import {Logo} from '../../Logo'; import {SidebarRouteTree} from '../Sidebar'; import type {RouteItem} from '../getRouteMeta'; import {siteConfig} from 'siteConfig'; import BrandMenu from './BrandMenu'; declare global { interface Window { __theme: string; __setPreferredTheme: (theme: string) => void; } } const darkIcon = ( ); const lightIcon = ( ); const languageIcon = ( ); const githubIcon = ( ); function Link({ href, children, ...props }: React.AnchorHTMLAttributes) { return ( {children} ); } function NavItem({url, isActive, children}: any) { return (
    {children}
    ); } function Kbd(props: {children?: React.ReactNode; wide?: boolean}) { const {wide, ...rest} = props; const width = wide ? 'w-10' : 'w-5'; return ( ); } export default function TopNav({ routeTree, breadcrumbs, section, }: { routeTree: RouteItem; breadcrumbs: RouteItem[]; section: 'learn' | 'reference' | 'community' | 'blog' | 'home' | 'unknown'; }) { const [isMenuOpen, setIsMenuOpen] = useState(false); const [showSearch, setShowSearch] = useState(false); const [isScrolled, setIsScrolled] = useState(false); const scrollParentRef = useRef(null); const {asPath} = useRouter(); // HACK. Fix up the data structures instead. if ((routeTree as any).routes.length === 1) { routeTree = (routeTree as any).routes[0]; } // While the overlay is open, disable body scroll. useEffect(() => { if (isMenuOpen) { const preferredScrollParent = scrollParentRef.current!; disableBodyScroll(preferredScrollParent); return () => enableBodyScroll(preferredScrollParent); } else { return undefined; } }, [isMenuOpen]); // Close the overlay on any navigation. useEffect(() => { setIsMenuOpen(false); }, [asPath]); // Also close the overlay if the window gets resized past mobile layout. // (This is also important because we don't want to keep the body locked!) useEffect(() => { const media = window.matchMedia(`(max-width: 1023px)`); function closeIfNeeded() { if (!media.matches) { setIsMenuOpen(false); } } closeIfNeeded(); media.addEventListener('change', closeIfNeeded); return () => { media.removeEventListener('change', closeIfNeeded); }; }, []); const scrollDetectorRef = useRef(null); useEffect(() => { const observer = new IntersectionObserver( (entries) => { entries.forEach((entry) => { setIsScrolled(!entry.isIntersecting); }); }, { root: null, rootMargin: `0px 0px`, threshold: 0, } ); observer.observe(scrollDetectorRef.current!); return () => observer.disconnect(); }, []); const onOpenSearch = useCallback(() => { startTransition(() => { setShowSearch(true); }); }, []); const onCloseSearch = useCallback(() => { setShowSearch(false); }, []); return ( <>
    {isMenuOpen && (
    )}
    ); } ================================================ FILE: src/components/Layout/TopNav/index.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ export {default as TopNav} from './TopNav'; ================================================ FILE: src/components/Layout/getRouteMeta.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ /** * While Next.js provides file-based routing, we still need to construct * a sidebar for navigation and provide each markdown page * previous/next links and titles. To do this, we construct a nested * route object that is infinitely nestable. */ export type RouteTag = | 'foundation' | 'intermediate' | 'advanced' | 'experimental' | 'deprecated'; export interface RouteItem { /** Page title (for the sidebar) */ title: string; /** Optional version flag for heading */ version?: 'canary' | 'major'; /** Optional page description for heading */ description?: string; /* Additional meta info for page tagging */ tags?: RouteTag[]; /** Path to page */ path?: string; /** Whether the entry is a heading */ heading?: boolean; /** List of sub-routes */ routes?: RouteItem[]; /** Adds a section header above the route item */ hasSectionHeader?: boolean; /** Title of section header */ sectionHeader?: string; /** Whether it should be omitted in breadcrumbs */ skipBreadcrumb?: boolean; } export interface Routes { /** List of routes */ routes: RouteItem[]; } /** Routing metadata about a given route and it's siblings and parent */ export interface RouteMeta { /** The previous route */ prevRoute?: RouteItem; /** The next route */ nextRoute?: RouteItem; /** The current route */ route?: RouteItem; /** Trail of parent routes */ breadcrumbs?: RouteItem[]; /** Order in the section */ order?: number; } type TraversalContext = RouteMeta & { currentIndex: number; }; export function getRouteMeta(cleanedPath: string, routeTree: RouteItem) { const breadcrumbs = getBreadcrumbs(cleanedPath, routeTree); const ctx: TraversalContext = { currentIndex: 0, }; buildRouteMeta(cleanedPath, routeTree, ctx); const {currentIndex: _, ...meta} = ctx; return { ...meta, breadcrumbs: breadcrumbs.length > 0 ? breadcrumbs : [routeTree], }; } // Performs a depth-first search to find the current route and its previous/next route function buildRouteMeta( searchPath: string, currentRoute: RouteItem, ctx: TraversalContext ) { ctx.currentIndex++; const {routes} = currentRoute; if (ctx.route && !ctx.nextRoute) { ctx.nextRoute = currentRoute; } if (currentRoute.path === searchPath) { ctx.route = currentRoute; ctx.order = ctx.currentIndex; // If we've found a deeper match, reset the previously stored next route. // TODO: this only works reliably if deeper matches are first in the tree. // We should revamp all of this to be more explicit. ctx.nextRoute = undefined; } if (!ctx.route) { ctx.prevRoute = currentRoute; } if (!routes) { return; } for (const route of routes) { buildRouteMeta(searchPath, route, ctx); } } // iterates the route tree from the current route to find its ancestors for breadcrumbs function getBreadcrumbs( path: string, currentRoute: RouteItem, breadcrumbs: RouteItem[] = [] ): RouteItem[] { if (currentRoute.path === path) { return breadcrumbs; } if (!currentRoute.routes) { return []; } for (const route of currentRoute.routes) { const childRoute = getBreadcrumbs(path, route, [ ...breadcrumbs, currentRoute, ]); if (childRoute?.length) { return childRoute; } } return []; } ================================================ FILE: src/components/Layout/useTocHighlight.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {useState, useRef, useEffect} from 'react'; const TOP_OFFSET = 85; export function getHeaderAnchors(): HTMLAnchorElement[] { return Array.prototype.filter.call( document.getElementsByClassName('mdx-header-anchor'), function (testElement) { return ( testElement.parentNode.nodeName === 'H1' || testElement.parentNode.nodeName === 'H2' || testElement.parentNode.nodeName === 'H3' ); } ); } /** * Sets up Table of Contents highlighting. */ export function useTocHighlight() { const [currentIndex, setCurrentIndex] = useState(0); const timeoutRef = useRef(null); useEffect(() => { function updateActiveLink() { const pageHeight = document.body.scrollHeight; const scrollPosition = window.scrollY + window.innerHeight; const headersAnchors = getHeaderAnchors(); if (scrollPosition >= 0 && pageHeight - scrollPosition <= 0) { // Scrolled to bottom of page. setCurrentIndex(headersAnchors.length - 1); return; } let index = -1; while (index < headersAnchors.length - 1) { const headerAnchor = headersAnchors[index + 1]; const {top} = headerAnchor.getBoundingClientRect(); if (top >= TOP_OFFSET) { break; } index += 1; } setCurrentIndex(Math.max(index, 0)); } function throttledUpdateActiveLink() { if (timeoutRef.current === null) { timeoutRef.current = window.setTimeout(() => { timeoutRef.current = null; updateActiveLink(); }, 100); } } document.addEventListener('scroll', throttledUpdateActiveLink); document.addEventListener('resize', throttledUpdateActiveLink); updateActiveLink(); return () => { if (timeoutRef.current != null) { clearTimeout(timeoutRef.current); timeoutRef.current = null; } document.removeEventListener('scroll', throttledUpdateActiveLink); document.removeEventListener('resize', throttledUpdateActiveLink); }; }, []); return { currentIndex, }; } ================================================ FILE: src/components/Logo.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import type {SVGProps} from 'react'; export function Logo(props: SVGProps) { return ( ); } ================================================ FILE: src/components/MDX/BlogCard.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import * as React from 'react'; import Link from 'next/link'; export interface BlogCardProps { title?: string; badge?: boolean; icon?: string; date?: string; url?: string; children?: React.ReactNode; } function BlogCard({title, badge, date, icon, url, children}: BlogCardProps) { return (

    {title}

    {icon === 'labs' && ( )} {icon === 'blog' && ( )} {date} {badge ? (
    New
    ) : null}
    {children} {children != null && (
    Read more
    )}
    ); } export default BlogCard; ================================================ FILE: src/components/MDX/Challenges/Challenge.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {useState} from 'react'; import cn from 'classnames'; import {Button} from 'components/Button'; import {ChallengeContents} from './Challenges'; import {IconHint} from '../../Icon/IconHint'; import {IconSolution} from '../../Icon/IconSolution'; import {IconArrowSmall} from '../../Icon/IconArrowSmall'; import {H4} from '../Heading'; interface ChallengeProps { isRecipes?: boolean; totalChallenges: number; currentChallenge: ChallengeContents; hasNextChallenge: boolean; handleClickNextChallenge: () => void; } export function Challenge({ isRecipes, totalChallenges, currentChallenge, hasNextChallenge, handleClickNextChallenge, }: ChallengeProps) { const [showHint, setShowHint] = useState(false); const [showSolution, setShowSolution] = useState(false); const toggleHint = () => { if (showSolution && !showHint) { setShowSolution(false); } setShowHint((hint) => !hint); }; const toggleSolution = () => { if (showHint && !showSolution) { setShowHint(false); } setShowSolution((solution) => !solution); }; return (

    {isRecipes ? '例' : 'チャレンジ'} {currentChallenge.order}/ {totalChallenges} :
    {currentChallenge.name}

    {currentChallenge.content}
    {currentChallenge.hint ? (
    ) : ( !isRecipes && ( ) )} {hasNextChallenge && ( )}
    {showHint && currentChallenge.hint} {showSolution && (

    答え

    {currentChallenge.solution}
    {hasNextChallenge && ( )}
    )}
    ); } ================================================ FILE: src/components/MDX/Challenges/Challenges.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {Children, useRef, useEffect, useState} from 'react'; import * as React from 'react'; import cn from 'classnames'; import {H2} from 'components/MDX/Heading'; import {H4} from 'components/MDX/Heading'; import {Challenge} from './Challenge'; import {Navigation} from './Navigation'; import {useRouter} from 'next/router'; interface ChallengesProps { children: React.ReactElement[]; isRecipes?: boolean; titleText?: string; titleId?: string; noTitle?: boolean; } export interface ChallengeContents { id: string; name: string; order: number; content: React.ReactNode; solution: React.ReactNode; hint?: React.ReactNode; } const parseChallengeContents = ( children: React.ReactElement[] ): ChallengeContents[] => { const contents: ChallengeContents[] = []; if (!children) { return contents; } let challenge: Partial = {}; let content: React.ReactElement[] = []; Children.forEach(children, (child) => { const {props, type} = child as React.ReactElement<{ children?: string; id?: string; }>; switch ((type as any).mdxName) { case 'Solution': { challenge.solution = child; challenge.content = content; contents.push(challenge as ChallengeContents); challenge = {}; content = []; break; } case 'Hint': { challenge.hint = child; break; } case 'h4': { challenge.order = contents.length + 1; challenge.name = props.children; challenge.id = props.id; break; } default: { content.push(child); } } }); return contents; }; enum QueuedScroll { INIT = 'init', NEXT = 'next', } export function Challenges({ children, isRecipes, noTitle, titleText = isRecipes ? '例を試す' : 'チャレンジ問題にトライ', titleId = isRecipes ? 'examples' : 'challenges', }: ChallengesProps) { const challenges = parseChallengeContents(children); const totalChallenges = challenges.length; const scrollAnchorRef = useRef(null); const queuedScrollRef = useRef(QueuedScroll.INIT); const [activeIndex, setActiveIndex] = useState(0); const currentChallenge = challenges[activeIndex]; const {asPath} = useRouter(); useEffect(() => { if (queuedScrollRef.current === QueuedScroll.INIT) { const initIndex = challenges.findIndex( (challenge) => challenge.id === asPath.split('#')[1] ); if (initIndex === -1) { queuedScrollRef.current = undefined; } else if (initIndex !== activeIndex) { setActiveIndex(initIndex); } } if (queuedScrollRef.current) { scrollAnchorRef.current!.scrollIntoView({ block: 'start', ...(queuedScrollRef.current === QueuedScroll.NEXT && { behavior: 'smooth', }), }); queuedScrollRef.current = undefined; } }, [activeIndex, asPath, challenges]); const handleChallengeChange = (index: number) => { setActiveIndex(index); }; const Heading = isRecipes ? H4 : H2; return (
    {!noTitle && ( {titleText} )} {totalChallenges > 1 && ( )}
    { setActiveIndex((i) => i + 1); queuedScrollRef.current = QueuedScroll.NEXT; }} />
    ); } ================================================ FILE: src/components/MDX/Challenges/Navigation.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {useRef, useCallback, useEffect, createRef} from 'react'; import cn from 'classnames'; import {IconChevron} from 'components/Icon/IconChevron'; import {ChallengeContents} from './Challenges'; import {debounce} from 'debounce'; export function Navigation({ challenges, handleChange, currentChallenge, isRecipes, }: { challenges: ChallengeContents[]; handleChange: (index: number) => void; currentChallenge: ChallengeContents; isRecipes?: boolean; }) { const containerRef = useRef(null); const challengesNavRef = useRef( challenges.map(() => createRef()) ); const scrollPos = currentChallenge.order - 1; const canScrollLeft = scrollPos > 0; const canScrollRight = scrollPos < challenges.length - 1; const handleScrollRight = () => { if (scrollPos < challenges.length - 1) { const currentNavRef = challengesNavRef.current[scrollPos + 1].current; if (!currentNavRef) { return; } if (containerRef.current) { containerRef.current.scrollLeft = currentNavRef.offsetLeft; } handleChange(scrollPos + 1); } }; const handleScrollLeft = () => { if (scrollPos > 0) { const currentNavRef = challengesNavRef.current[scrollPos - 1].current; if (!currentNavRef) { return; } if (containerRef.current) { containerRef.current.scrollLeft = currentNavRef.offsetLeft; } handleChange(scrollPos - 1); } }; const handleSelectNav = (index: number) => { const currentNavRef = challengesNavRef.current[index].current; if (containerRef.current) { containerRef.current.scrollLeft = currentNavRef?.offsetLeft || 0; } handleChange(index); }; const handleResize = useCallback(() => { if (containerRef.current) { const el = containerRef.current; el.scrollLeft = challengesNavRef.current[scrollPos].current?.offsetLeft || 0; } }, [containerRef, challengesNavRef, scrollPos]); useEffect(() => { handleResize(); const debouncedHandleResize = debounce(handleResize, 200); window.addEventListener('resize', debouncedHandleResize); return () => { window.removeEventListener('resize', debouncedHandleResize); }; }, [handleResize]); return (
    {challenges.map(({name, id, order}, index) => ( ))}
    ); } ================================================ FILE: src/components/MDX/Challenges/index.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import * as React from 'react'; export {Challenges} from './Challenges'; export function Hint({children}: {children: React.ReactNode}) { return
    {children}
    ; } export function Solution({children}: {children: React.ReactNode}) { return
    {children}
    ; } ================================================ FILE: src/components/MDX/CodeBlock/CodeBlock.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import cn from 'classnames'; import {HighlightStyle} from '@codemirror/language'; import {highlightTree} from '@lezer/highlight'; import {javascript} from '@codemirror/lang-javascript'; import {html} from '@codemirror/lang-html'; import {css} from '@codemirror/lang-css'; import rangeParser from 'parse-numeric-range'; import {tags} from '@lezer/highlight'; import {CustomTheme} from '../Sandpack/Themes'; interface InlineHighlight { step: number; line: number; startColumn: number; endColumn: number; } const jsxLang = javascript({jsx: true, typescript: false}); const cssLang = css(); const htmlLang = html(); const CodeBlock = function CodeBlock({ children: { props: {className = 'language-js', children: code = '', meta}, }, noMargin, noShadow, onLineHover, }: { children: React.ReactNode & { props: { className: string; children?: string; meta?: string; }; }; className?: string; noMargin?: boolean; noShadow?: boolean; onLineHover?: (lineNumber: number | null) => void; }) { code = code.trimEnd(); let lang = jsxLang; if (className === 'language-css') { lang = cssLang; } else if (className === 'language-html') { lang = htmlLang; } const tree = lang.language.parser.parse(code); let tokenStarts = new Map(); let tokenEnds = new Map(); const highlightTheme = getSyntaxHighlight(CustomTheme); highlightTree(tree, highlightTheme, (from, to, className) => { tokenStarts.set(from, className); tokenEnds.set(to, className); }); const highlightedLines = new Map(); const lines = code.split('\n'); const lineDecorators = getLineDecorators(code, meta); for (let decorator of lineDecorators) { highlightedLines.set(decorator.line - 1, decorator.className); } const inlineDecorators = getInlineDecorators(code, meta); const decoratorStarts = new Map(); const decoratorEnds = new Map(); for (let decorator of inlineDecorators) { // Find where inline highlight starts and ends. let decoratorStart = 0; for (let i = 0; i < decorator.line - 1; i++) { decoratorStart += lines[i].length + 1; } decoratorStart += decorator.startColumn; const decoratorEnd = decoratorStart + (decorator.endColumn - decorator.startColumn); if (decoratorStarts.has(decoratorStart)) { throw Error('Already opened decorator at ' + decoratorStart); } decoratorStarts.set(decoratorStart, decorator.className); if (decoratorEnds.has(decoratorEnd)) { throw Error('Already closed decorator at ' + decoratorEnd); } decoratorEnds.set(decoratorEnd, decorator.className); } // Produce output based on tokens and decorators. // We assume tokens never overlap other tokens, and // decorators never overlap with other decorators. // However, tokens and decorators may mutually overlap. // In that case, decorators always take precedence. let currentDecorator = null; let currentToken = null; let buffer = ''; let lineIndex = 0; let lineOutput = []; let finalOutput = []; for (let i = 0; i < code.length; i++) { if (tokenEnds.has(i)) { if (!currentToken) { throw Error('Cannot close token at ' + i + ' because it was not open.'); } if (!currentDecorator) { lineOutput.push( {buffer} ); buffer = ''; } currentToken = null; } if (decoratorEnds.has(i)) { if (!currentDecorator) { throw Error( 'Cannot close decorator at ' + i + ' because it was not open.' ); } lineOutput.push( {buffer} ); buffer = ''; currentDecorator = null; } if (decoratorStarts.has(i)) { if (currentDecorator) { throw Error( 'Cannot open decorator at ' + i + ' before closing last one.' ); } if (currentToken) { lineOutput.push( {buffer} ); buffer = ''; } else { lineOutput.push(buffer); buffer = ''; } currentDecorator = decoratorStarts.get(i); } if (tokenStarts.has(i)) { if (currentToken) { throw Error('Cannot open token at ' + i + ' before closing last one.'); } currentToken = tokenStarts.get(i); if (!currentDecorator) { lineOutput.push(buffer); buffer = ''; } } if (code[i] === '\n') { lineOutput.push(buffer); buffer = ''; const currentLineIndex = lineIndex; finalOutput.push(
    onLineHover(currentLineIndex) : undefined }> {lineOutput}
    ); lineOutput = []; lineIndex++; } else { buffer += code[i]; } } if (currentDecorator) { lineOutput.push( {buffer} ); } else if (currentToken) { lineOutput.push( {buffer} ); } else { lineOutput.push(buffer); } finalOutput.push(
    onLineHover(lineIndex) : undefined}> {lineOutput}
    ); return (
                   onLineHover(null) : undefined
                    }>
                    {finalOutput}
                  
                
    ); }; export default CodeBlock; function classNameToken(name: string): string { return `sp-syntax-${name}`; } function getSyntaxHighlight(theme: any): HighlightStyle { return HighlightStyle.define([ {tag: tags.link, textdecorator: 'underline'}, {tag: tags.emphasis, fontStyle: 'italic'}, {tag: tags.strong, fontWeight: 'bold'}, { tag: tags.keyword, class: classNameToken('keyword'), }, { tag: [tags.atom, tags.number, tags.bool], class: classNameToken('static'), }, { tag: tags.standard(tags.tagName), class: classNameToken('tag'), }, {tag: tags.variableName, class: classNameToken('plain')}, { // Highlight function call tag: tags.function(tags.variableName), class: classNameToken('definition'), }, { // Highlight function definition differently (eg: functional component def in React) tag: [tags.definition(tags.function(tags.variableName)), tags.tagName], class: classNameToken('definition'), }, { tag: tags.propertyName, class: classNameToken('property'), }, { tag: [tags.literal, tags.inserted], class: classNameToken(theme.syntax.string ? 'string' : 'static'), }, { tag: tags.punctuation, class: classNameToken('punctuation'), }, { tag: [tags.comment, tags.quote], class: classNameToken('comment'), }, ]); } function getLineDecorators( code: string, meta?: string ): Array<{ line: number; className: string; }> { if (!meta) { return []; } const linesToHighlight = getHighlightLines(meta); const highlightedLineConfig = linesToHighlight.map((line) => { return { className: 'bg-github-highlight dark:bg-opacity-10', line, }; }); return highlightedLineConfig; } function getInlineDecorators( code: string, meta?: string ): Array<{ step: number; line: number; startColumn: number; endColumn: number; className: string; }> { if (!meta) { return []; } const inlineHighlightLines = getInlineHighlights(meta, code); const inlineHighlightConfig = inlineHighlightLines.map( (line: InlineHighlight) => ({ ...line, elementAttributes: {'data-step': `${line.step}`}, className: cn( 'code-step bg-opacity-10 dark:bg-opacity-20 relative rounded px-1 py-[1.5px] border-b-[2px] border-opacity-60', { 'bg-blue-40 border-blue-40 text-blue-60 dark:text-blue-30': line.step === 1, 'bg-yellow-40 border-yellow-40 text-yellow-60 dark:text-yellow-30': line.step === 2, 'bg-purple-40 border-purple-40 text-purple-60 dark:text-purple-30': line.step === 3, 'bg-green-40 border-green-40 text-green-60 dark:text-green-30': line.step === 4, // TODO: Some codeblocks use up to 6 steps. } ), }) ); return inlineHighlightConfig; } /** * * @param meta string provided after the language in a markdown block * @returns array of lines to highlight * @example * ```js {1-3,7} [[1, 1, 20, 33], [2, 4, 4, 8]] App.js active * ... * ``` * * -> The meta is `{1-3,7} [[1, 1, 20, 33], [2, 4, 4, 8]] App.js active` */ function getHighlightLines(meta: string): number[] { const HIGHLIGHT_REGEX = /{([\d,-]+)}/; const parsedMeta = HIGHLIGHT_REGEX.exec(meta); if (!parsedMeta) { return []; } return rangeParser(parsedMeta[1]); } /** * * @param meta string provided after the language in a markdown block * @returns InlineHighlight[] * @example * ```js {1-3,7} [[1, 1, 'count'], [2, 4, 'setCount']] App.js active * ... * ``` * * -> The meta is `{1-3,7} [[1, 1, 'count', [2, 4, 'setCount']] App.js active` */ function getInlineHighlights(meta: string, code: string) { const INLINE_HEIGHT_REGEX = /(\[\[.*\]\])/; const parsedMeta = INLINE_HEIGHT_REGEX.exec(meta); if (!parsedMeta) { return []; } const lines = code.split('\n'); const encodedHighlights = JSON.parse(parsedMeta[1]); return encodedHighlights.map(([step, lineNo, substr, fromIndex]: any[]) => { const line = lines[lineNo - 1]; let index = line.indexOf(substr); const lastIndex = line.lastIndexOf(substr); if (index !== lastIndex) { if (fromIndex === undefined) { throw Error( "Found '" + substr + "' twice. Specify fromIndex as the fourth value in the tuple." ); } index = line.indexOf(substr, fromIndex); } if (index === -1) { throw Error("Could not find: '" + substr + "'"); } return { step, line: lineNo, startColumn: index, endColumn: index + substr.length, }; }); } ================================================ FILE: src/components/MDX/CodeBlock/index.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import * as React from 'react'; import cn from 'classnames'; import {lazy, memo, Suspense} from 'react'; const CodeBlock = lazy(() => import('./CodeBlock')); export default memo(function CodeBlockWrapper(props: { children: React.ReactNode & { props: { className: string; children: string; meta?: string; }; }; isFromPackageImport: boolean; noMargin?: boolean; noMarkers?: boolean; }): any { const {children, isFromPackageImport} = props; return (

    {children}

    }>
    ); }); ================================================ FILE: src/components/MDX/CodeDiagram.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {Children} from 'react'; import * as React from 'react'; import CodeBlock from './CodeBlock'; interface CodeDiagramProps { children: React.ReactNode; flip?: boolean; } export function CodeDiagram({children, flip = false}: CodeDiagramProps) { const illustration = Children.toArray(children).filter((child: any) => { return child.type === 'img'; }); const content = Children.toArray(children).map((child: any) => { if (child.type?.mdxName === 'pre') { return ( ); } else if (child.type === 'img') { return null; } else { return child; } }); if (flip) { return (
    {illustration}
    {content}
    ); } return (
    {content}
    {illustration}
    ); } ================================================ FILE: src/components/MDX/ConsoleBlock.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {isValidElement} from 'react'; import * as React from 'react'; import cn from 'classnames'; import {IconWarning} from '../Icon/IconWarning'; import {IconError} from '../Icon/IconError'; type LogLevel = 'warning' | 'error' | 'info'; interface ConsoleBlockProps { level?: LogLevel; children: React.ReactNode; } interface ConsoleBlockMultiProps { children: React.ReactNode; } const Box = ({ width = '60px', height = '17px', className, customStyles, }: { width?: string; height?: string; className?: string; customStyles?: Record; }) => (
    ); export function ConsoleBlock({level = 'error', children}: ConsoleBlockProps) { let message: React.ReactNode | null; if (typeof children === 'string') { message = children; } else if (isValidElement(children)) { message = (children as React.ReactElement<{children?: React.ReactNode}>) .props.children; } return (
    Console
    {level === 'error' && } {level === 'warning' && }
    {message}
    ); } export function ConsoleBlockMulti({children}: ConsoleBlockMultiProps) { return (
    Console
    {children}
    ); } export function ConsoleLogLine({children, level}: ConsoleBlockProps) { let message: React.ReactNode | null; if (typeof children === 'string') { message = children; } else if (isValidElement(children)) { message = (children as React.ReactElement<{children?: React.ReactNode}>) .props.children; } else if (Array.isArray(children)) { message = children.reduce((result, child) => { if (typeof child === 'string') { result += child; } else if (isValidElement(child)) { // @ts-ignore result += child.props.children; } return result; }, ''); } return (
    {level === 'error' && ( )} {level === 'warning' && ( )}
    {message}
    ); } ================================================ FILE: src/components/MDX/Diagram.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import Image from 'next/image'; interface DiagramProps { name: string; alt: string; height: number; width: number; children: string; captionPosition: 'top' | 'bottom' | null; } function Caption({text}: {text: string}) { return (
    {text}
    ); } export function Diagram({ name, alt, height, width, children, captionPosition, }: DiagramProps) { return (
    {captionPosition === 'top' && }
    {alt}
    {alt}
    {(!captionPosition || captionPosition === 'bottom') && ( )}
    ); } export default Diagram; ================================================ FILE: src/components/MDX/DiagramGroup.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {ReactNode} from 'react'; interface DiagramGroupProps { children: ReactNode; } export function DiagramGroup({children}: DiagramGroupProps) { return (
    {children}
    ); } export default DiagramGroup; ================================================ FILE: src/components/MDX/ErrorDecoder.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ import {useEffect, useState} from 'react'; import {useErrorDecoderParams} from '../ErrorDecoderContext'; import cn from 'classnames'; function replaceArgs( msg: string, argList: Array, replacer = '[missing argument]' ): string { let argIdx = 0; return msg.replace(/%s/g, function () { const arg = argList[argIdx++]; // arg can be an empty string: ?args[0]=&args[1]=count return arg === undefined ? replacer : arg; }); } /** * Sindre Sorhus * Released under MIT license * https://github.com/sindresorhus/linkify-urls/blob/b2397096df152e2f799011f7a48e5f73b4bf1c7e/index.js#L5C1-L7C1 * * The regex is used to extract URL from the string for linkify. */ const urlRegex = () => /((?:https?(?::\/\/))(?:www\.)?(?:[a-zA-Z\d-_.]+(?:(?:\.|@)[a-zA-Z\d]{2,})|localhost)(?:(?:[-a-zA-Z\d:%_+.~#!?&//=@]*)(?:[,](?![\s]))*)*)/g; // When the message contains a URL (like https://fb.me/react-refs-must-have-owner), // make it a clickable link. function urlify(str: string): React.ReactNode[] { const segments = str.split(urlRegex()); return segments.map((message, i) => { if (i % 2 === 1) { return ( {message} ); } return message; }); } // `?args[]=foo&args[]=bar` // or `// ?args[0]=foo&args[1]=bar` function parseQueryString(search: string): Array { const rawQueryString = search.substring(1); if (!rawQueryString) { return []; } const args: Array = []; const queries = rawQueryString.split('&'); for (let i = 0; i < queries.length; i++) { const query = decodeURIComponent(queries[i]); if (query.startsWith('args[')) { args.push(query.slice(query.indexOf(']=') + 2)); } } return args; } export default function ErrorDecoder() { const {errorMessage, errorCode} = useErrorDecoderParams(); /** error messages that contain %s require reading location.search */ const hasParams = errorMessage?.includes('%s'); const [message, setMessage] = useState(() => errorMessage ? urlify(errorMessage) : null ); const [isReady, setIsReady] = useState(errorMessage == null || !hasParams); useEffect(() => { if (errorMessage == null || !hasParams) { return; } const args = parseQueryString(window.location.search); let message = errorMessage; if (errorCode === '418') { // Hydration errors have a %s for the diff, but we don't add that to the args for security reasons. message = message.replace(/%s$/, ''); // Before React 19.1, the error message didn't have an arg, and was always HTML. if (args.length === 0) { args.push('HTML'); } else if (args.length === 1 && args[0] === '') { args[0] = 'HTML'; } } setMessage(urlify(replaceArgs(message, args, '[missing argument]'))); setIsReady(true); }, [errorCode, hasParams, errorMessage]); return ( {message} ); } ================================================ FILE: src/components/MDX/ExpandableCallout.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import * as React from 'react'; import cn from 'classnames'; import {IconNote} from '../Icon/IconNote'; import {IconWarning} from '../Icon/IconWarning'; import {IconPitfall} from '../Icon/IconPitfall'; import {IconCanary} from '../Icon/IconCanary'; import {IconRocket} from '../Icon/IconRocket'; type CalloutVariants = | 'deprecated' | 'pitfall' | 'note' | 'wip' | 'canary' | 'experimental' | 'rc' | 'major' | 'rsc'; interface ExpandableCalloutProps { children: React.ReactNode; type: CalloutVariants; } const variantMap = { deprecated: { title: 'Deprecated', Icon: IconWarning, containerClasses: 'bg-red-5 dark:bg-red-60 dark:bg-opacity-20', textColor: 'text-red-50 dark:text-red-40', overlayGradient: 'linear-gradient(rgba(249, 247, 243, 0), rgba(249, 247, 243, 1)', }, note: { title: '補足', Icon: IconNote, containerClasses: 'bg-green-5 dark:bg-green-60 dark:bg-opacity-20 text-primary dark:text-primary-dark text-lg', textColor: 'text-green-60 dark:text-green-40', overlayGradient: 'linear-gradient(rgba(245, 249, 248, 0), rgba(245, 249, 248, 1)', }, rc: { title: 'RC', Icon: IconCanary, containerClasses: 'bg-gray-5 dark:bg-gray-60 dark:bg-opacity-20 text-primary dark:text-primary-dark text-lg', textColor: 'text-gray-60 dark:text-gray-30', overlayGradient: 'linear-gradient(rgba(245, 249, 248, 0), rgba(245, 249, 248, 1)', }, canary: { title: 'Canary', Icon: IconCanary, containerClasses: 'bg-gray-5 dark:bg-gray-60 dark:bg-opacity-20 text-primary dark:text-primary-dark text-lg', textColor: 'text-gray-60 dark:text-gray-30', overlayGradient: 'linear-gradient(rgba(245, 249, 248, 0), rgba(245, 249, 248, 1)', }, experimental: { title: '実験的機能', Icon: IconCanary, containerClasses: 'bg-green-5 dark:bg-green-60 dark:bg-opacity-20 text-primary dark:text-primary-dark text-lg', textColor: 'text-green-60 dark:text-green-40', overlayGradient: 'linear-gradient(rgba(245, 249, 248, 0), rgba(245, 249, 248, 1)', }, pitfall: { title: '落とし穴', Icon: IconPitfall, containerClasses: 'bg-yellow-5 dark:bg-yellow-60 dark:bg-opacity-20', textColor: 'text-yellow-50 dark:text-yellow-40', overlayGradient: 'linear-gradient(rgba(249, 247, 243, 0), rgba(249, 247, 243, 1)', }, wip: { title: '準備中', Icon: IconNote, containerClasses: 'bg-yellow-5 dark:bg-yellow-60 dark:bg-opacity-20', textColor: 'text-yellow-50 dark:text-yellow-40', overlayGradient: 'linear-gradient(rgba(249, 247, 243, 0), rgba(249, 247, 243, 1)', }, major: { title: 'React 19', Icon: IconRocket, containerClasses: 'bg-blue-10 dark:bg-blue-60 dark:bg-opacity-20', textColor: 'text-blue-50 dark:text-blue-40', overlayGradient: 'linear-gradient(rgba(249, 247, 243, 0), rgba(249, 247, 243, 1)', }, rsc: { title: 'React Server Components', Icon: null, containerClasses: 'bg-blue-10 dark:bg-blue-60 dark:bg-opacity-20', textColor: 'text-blue-50 dark:text-blue-40', overlayGradient: 'linear-gradient(rgba(249, 247, 243, 0), rgba(249, 247, 243, 1)', }, }; function ExpandableCallout({children, type = 'note'}: ExpandableCalloutProps) { const variant = variantMap[type]; return (

    {variant.Icon && ( )} {variant.title}

    {children}
    ); } export default ExpandableCallout; ================================================ FILE: src/components/MDX/ExpandableExample.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import * as React from 'react'; import cn from 'classnames'; import {IconChevron} from '../Icon/IconChevron'; import {IconDeepDive} from '../Icon/IconDeepDive'; import {IconCodeBlock} from '../Icon/IconCodeBlock'; import {Button} from '../Button'; import {H4} from './Heading'; import {useRouter} from 'next/router'; import {useEffect, useRef, useState} from 'react'; interface ExpandableExampleProps { children: React.ReactNode; excerpt?: string; type: 'DeepDive' | 'Example'; } function ExpandableExample({children, excerpt, type}: ExpandableExampleProps) { if (!Array.isArray(children) || children[0].type.mdxName !== 'h4') { throw Error( `Expandable content ${type} is missing a corresponding title at the beginning` ); } const isDeepDive = type === 'DeepDive'; const isExample = type === 'Example'; const id = children[0].props.id; const {asPath} = useRouter(); const shouldAutoExpand = id === asPath.split('#')[1]; const queuedExpandRef = useRef(shouldAutoExpand); const [isExpanded, setIsExpanded] = useState(false); useEffect(() => { if (queuedExpandRef.current) { queuedExpandRef.current = false; setIsExpanded(true); } }, []); return (
    { setIsExpanded(e.currentTarget!.open); }} className={cn( 'my-12 rounded-2xl shadow-inner-border dark:shadow-inner-border-dark relative', { 'dark:bg-opacity-20 dark:bg-purple-60 bg-purple-5': isDeepDive, 'dark:bg-opacity-20 dark:bg-yellow-60 bg-yellow-5': isExample, } )}> { // We toggle using a button instead of this whole area, // with an escape case for the header anchor link if (!(e.target instanceof SVGElement)) { e.preventDefault(); } }}>
    {isDeepDive && ( <> さらに深く知る )} {isExample && ( <> 例 )}

    {children[0].props.children}

    {excerpt &&
    {excerpt}
    }
    {children.slice(1)}
    ); } export default ExpandableExample; ================================================ FILE: src/components/MDX/Heading.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import cn from 'classnames'; import * as React from 'react'; import {forwardRefWithAs} from 'utils/forwardRefWithAs'; export interface HeadingProps { className?: string; isPageAnchor?: boolean; children: React.ReactNode; id?: string; as?: any; } const Heading = forwardRefWithAs(function Heading( {as: Comp = 'div', className, children, id, isPageAnchor = true, ...props}, ref ) { let label = 'Link for this heading'; if (typeof children === 'string') { label = 'Link for ' + children; } return ( {children} {isPageAnchor && ( )} ); }); export const H1 = ({className, ...props}: HeadingProps) => ( ); export const H2 = ({className, ...props}: HeadingProps) => ( ); export const H3 = ({className, ...props}: HeadingProps) => ( ); export const H4 = ({className, ...props}: HeadingProps) => ( ); export const H5 = ({className, ...props}: HeadingProps) => ( ); ================================================ FILE: src/components/MDX/InlineCode.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import cn from 'classnames'; import type {HTMLAttributes} from 'react'; interface InlineCodeProps { isLink?: boolean; meta?: string; } function InlineCode({ isLink, ...props }: HTMLAttributes & InlineCodeProps) { return ( in case of RTL languages to avoid like `()console.log` to be rendered as `console.log()` className={cn( 'inline text-code text-secondary dark:text-secondary-dark px-1 rounded-md no-underline', { 'bg-gray-30 bg-opacity-10 py-px': !isLink, 'bg-highlight dark:bg-highlight-dark py-0': isLink, } )} {...props} /> ); } export default InlineCode; ================================================ FILE: src/components/MDX/Intro.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import * as React from 'react'; export interface IntroProps { children?: React.ReactNode; } function Intro({children}: IntroProps) { return (
    {children}
    ); } export default Intro; ================================================ FILE: src/components/MDX/LanguagesContext.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {createContext} from 'react'; export type LanguageItem = { code: string; name: string; enName: string; }; export type Languages = Array; export const LanguagesContext = createContext(null); ================================================ FILE: src/components/MDX/Link.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {Children, cloneElement} from 'react'; import NextLink from 'next/link'; import cn from 'classnames'; import {ExternalLink} from 'components/ExternalLink'; function Link({ href, className, children, ...props }: React.AnchorHTMLAttributes) { const classes = 'inline text-link dark:text-link-dark border-b border-link border-opacity-0 hover:border-opacity-100 duration-100 ease-in transition leading-normal'; const modifiedChildren = Children.toArray(children).map((child: any) => { if (child.type?.mdxName && child.type?.mdxName === 'inlineCode') { return cloneElement(child, { isLink: true, }); } return child; }); if (!href) { // eslint-disable-next-line jsx-a11y/anchor-has-content return ; } // Tweak external links (added by ja.react.dev team) href = href .replace( 'https://developer.mozilla.org/en-US/', 'https://developer.mozilla.org/' ) .replace('https://reactjs.org/', 'https://ja.reactjs.org/') .replace('https://legacy.reactjs.org/', 'https://ja.legacy.reactjs.org/'); return ( <> {href.startsWith('https://') ? ( {modifiedChildren} ) : href.startsWith('#') ? ( // eslint-disable-next-line jsx-a11y/anchor-has-content {modifiedChildren} ) : ( {modifiedChildren} )} ); } export default Link; ================================================ FILE: src/components/MDX/MDXComponents.module.css ================================================ /* * Copyright (c) Facebook, Inc. and its affiliates. */ /* Stop purging. */ .markdown p { @apply mb-4 leading-7 whitespace-pre-wrap text-gray-70; } .markdown ol { @apply mb-4 ms-8 list-decimal; } .markdown ul { @apply mb-4 ms-8 list-disc; } .markdown h1 { @apply mb-6 text-4xl font-extrabold tracking-tight; } .markdown h2 { @apply mt-12 mb-4 text-3xl font-extrabold tracking-tight; } .markdown h3 { @apply mt-8 mb-3 text-2xl font-extrabold tracking-tight; } .markdown h4 { @apply mt-8 mb-3 text-xl font-extrabold tracking-tight; } .markdown code { @apply text-gray-70 bg-card dark:bg-card-dark p-1 rounded-lg no-underline; font-size: 90%; } .markdown li { @apply mb-2; } .markdown a { @apply inline text-link dark:text-link-dark break-normal border-b border-link border-opacity-0 hover:border-opacity-100 duration-100 ease-in transition leading-normal; } ================================================ FILE: src/components/MDX/MDXComponents.tsx ================================================ /** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ /* * Copyright (c) Facebook, Inc. and its affiliates. */ import {Children, useContext, useMemo} from 'react'; import * as React from 'react'; import cn from 'classnames'; import type {HTMLAttributes} from 'react'; import CodeBlock from './CodeBlock'; import {CodeDiagram} from './CodeDiagram'; import {ConsoleBlock, ConsoleLogLine, ConsoleBlockMulti} from './ConsoleBlock'; import ExpandableCallout from './ExpandableCallout'; import ExpandableExample from './ExpandableExample'; import {H1, H2, H3, H4, H5} from './Heading'; import InlineCode from './InlineCode'; import Intro from './Intro'; import BlogCard from './BlogCard'; import Link from './Link'; import {PackageImport} from './PackageImport'; import Recap from './Recap'; import Sandpack from './Sandpack'; import SandpackWithHTMLOutput from './SandpackWithHTMLOutput'; import Diagram from './Diagram'; import DiagramGroup from './DiagramGroup'; import SimpleCallout from './SimpleCallout'; import TerminalBlock from './TerminalBlock'; import YouWillLearnCard from './YouWillLearnCard'; import {Challenges, Hint, Solution} from './Challenges'; import {IconNavArrow} from '../Icon/IconNavArrow'; import ButtonLink from 'components/ButtonLink'; import {TocContext} from './TocContext'; import type {Toc, TocItem} from './TocContext'; import {TeamMember} from './TeamMember'; import {LanguagesContext} from './LanguagesContext'; import {finishedTranslations} from 'utils/finishedTranslations'; import ErrorDecoder from './ErrorDecoder'; import {IconCanary} from '../Icon/IconCanary'; import {IconExperimental} from 'components/Icon/IconExperimental'; function CodeStep({children, step}: {children: any; step: number}) { return ( {children} ); } const P = (p: HTMLAttributes) => (

    ); const Strong = (strong: HTMLAttributes) => ( ); const OL = (p: HTMLAttributes) => (

      ); const LI = (p: HTMLAttributes) => (
    1. ); const UL = (p: HTMLAttributes) => (
        ); const Divider = () => (
        ); const Wip = ({children}: {children: React.ReactNode}) => ( {children} ); const Pitfall = ({children}: {children: React.ReactNode}) => ( {children} ); const Deprecated = ({children}: {children: React.ReactNode}) => ( {children} ); const Note = ({children}: {children: React.ReactNode}) => ( {children} ); const Canary = ({children}: {children: React.ReactNode}) => ( {children} ); const RC = ({children}: {children: React.ReactNode}) => ( {children} ); const Experimental = ({children}: {children: React.ReactNode}) => ( {children} ); const NextMajor = ({children}: {children: React.ReactNode}) => ( {children} ); const RSC = ({children}: {children: React.ReactNode}) => ( {children} ); const CanaryBadge = ({title}: {title: string}) => ( Canary only ); const ExperimentalBadge = ({title}: {title: string}) => ( Experimental only ); const NextMajorBadge = ({title}: {title: string}) => ( React 19 ); const RSCBadge = ({title}: {title: string}) => ( RSC ); const Blockquote = ({children, ...props}: HTMLAttributes) => { return (
        {children}
        ); }; function LearnMore({ children, path, }: { title: string; path?: string; children: any; }) { return ( <>

        Ready to learn this topic?

        {children} {path ? ( Read More ) : null}

        ); } function ReadBlogPost({path}: {path: string}) { return ( Read Post ); } function Math({children}: {children: any}) { return ( {children} ); } function MathI({children}: {children: any}) { return ( {children} ); } function YouWillLearn({ children, isChapter, }: { children: any; isChapter?: boolean; }) { let title = isChapter ? 'この章で学ぶこと' : 'このページで学ぶこと'; return {children}; } // TODO: typing. function Recipes(props: any) { return ; } function AuthorCredit({ author = 'Rachel Lee Nabors', authorLink = 'https://nearestnabors.com/', }: { author: string; authorLink: string; }) { return (

        Illustrated by{' '} {authorLink ? ( {author} ) : ( author )}

        ); } const IllustrationContext = React.createContext<{ isInBlock?: boolean; }>({ isInBlock: false, }); function Illustration({ caption, src, alt, author, authorLink, }: { caption: string; src: string; alt: string; author: string; authorLink: string; }) { const {isInBlock} = React.useContext(IllustrationContext); return (
        {alt} {caption ? (
        {caption}
        ) : null}
        {!isInBlock && }
        ); } const isInBlockTrue = {isInBlock: true}; function IllustrationBlock({ sequential, author, authorLink, children, }: { author: string; authorLink: string; sequential: boolean; children: any; }) { const imageInfos = Children.toArray(children).map( (child: any) => child.props ); const images = imageInfos.map((info, index) => (
        {info.alt}
        {info.caption ? (
        {info.caption}
        ) : null}
        )); return (
        {sequential ? (
          {images.map((x: any, i: number) => (
        1. {x}
        2. ))}
        ) : (
        {images}
        )}
        ); } type NestedTocRoot = { item: null; children: Array; }; type NestedTocNode = { item: TocItem; children: Array; }; function calculateNestedToc(toc: Toc): NestedTocRoot { const currentAncestors = new Map(); const root: NestedTocRoot = { item: null, children: [], }; const startIndex = 1; // Skip "Overview" for (let i = startIndex; i < toc.length; i++) { const item = toc[i]; const currentParent: NestedTocNode | NestedTocRoot = currentAncestors.get(item.depth - 1) || root; const node: NestedTocNode = { item, children: [], }; currentParent.children.push(node); currentAncestors.set(item.depth, node); } return root; } function InlineToc() { const toc = useContext(TocContext); const root = useMemo(() => calculateNestedToc(toc), [toc]); if (root.children.length < 2) { return null; } return ; } function InlineTocItem({items}: {items: Array}) { return (
          {items.map((node) => (
        • {node.item.text} {node.children.length > 0 && }
        • ))}
        ); } type TranslationProgress = 'complete' | 'in-progress'; function LanguageList({progress}: {progress: TranslationProgress}) { const allLanguages = React.useContext(LanguagesContext) ?? []; const languages = allLanguages .filter( ({code}) => code !== 'en' && (progress === 'complete' ? finishedTranslations.includes(code) : !finishedTranslations.includes(code)) ) .sort((a, b) => a.enName.localeCompare(b.enName)); return (
          {languages.map(({code, name, enName}) => { return (
        • {enName} ({name}) {' '} —{' '} Contribute
        • ); })}
        ); } function YouTubeIframe(props: any) { return (
        ``` ```js src/index.js import { flushReadableStreamToFrame, getUser, Postponed, sleep, } from "./demo-helpers"; import { StrictMode, Suspense, use, useEffect } from "react"; import { prerender } from "react-dom/static"; import { resume } from "react-dom/server"; import { hydrateRoot } from "react-dom/client"; function Header() { return
        Me and my descendants can be prerendered
        ; } const { promise: cookies, resolve: resolveCookies } = Promise.withResolvers(); function Main() { const { sessionID } = use(cookies); const user = getUser(sessionID); useEffect(() => { console.log("reached interactivity!"); }, []); return (
        Hello, {user.name}!
        ); } function Shell({ children }) { // In a real app, this is where you would put your html and body. // We're just using tags here we can include in an existing body for demonstration purposes return ( {children} ); } function App() { return (
        ); } async function main(frame) { // Layer 1 const controller = new AbortController(); const prerenderedApp = prerender(, { signal: controller.signal, onError(error) { if (error instanceof Postponed) { } else { console.error(error); } }, }); // We're immediately aborting in a macrotask. // Any data fetching that's not available synchronously, or in a microtask, will not have finished. setTimeout(() => { controller.abort(new Postponed()); }); const { prelude, postponed } = await prerenderedApp; await flushReadableStreamToFrame(prelude, frame); // Layer 2 // Just waiting here for demonstration purposes. // In a real app, the prelude and postponed state would've been serialized in Layer 1 and Layer would deserialize them. // The prelude content could be flushed immediated as plain HTML while // React is continuing to render from where the prerender left off. await sleep(2000); // You would get the cookies from the incoming HTTP request resolveCookies({ sessionID: "abc" }); const stream = await resume(, postponed); await flushReadableStreamToFrame(stream, frame); // Layer 3 // Just waiting here for demonstration purposes. await sleep(2000); hydrateRoot(frame.contentWindow.document, ); } main(document.getElementById("container")); ``` ```js src/demo-helpers.js export async function flushReadableStreamToFrame(readable, frame) { const document = frame.contentWindow.document; const decoder = new TextDecoder(); for await (const chunk of readable) { const partialHTML = decoder.decode(chunk); document.write(partialHTML); } } // This doesn't need to be an error. // You can use any other means to check if an error during prerender was // from an intentional abort or a real error. export class Postponed extends Error {} // We're just hardcoding a session here. export function getUser(sessionID) { return { name: "Alice", }; } export function sleep(timeoutMS) { return new Promise((resolve) => { setTimeout(() => { resolve(); }, timeoutMS); }); } ``` ### Further reading {/*further-reading*/} Resuming behaves like `renderToReadableStream`. For more examples, check out the [usage section of `renderToReadableStream`](/reference/react-dom/server/renderToReadableStream#usage). The [usage section of `prerender`](/reference/react-dom/static/prerender#usage) includes examples of how to use `prerender` specifically. ================================================ FILE: src/content/reference/react-dom/server/resumeToPipeableStream.md ================================================ --- title: resumeToPipeableStream --- `resumeToPipeableStream` streams a pre-rendered React tree to a pipeable [Node.js Stream.](https://nodejs.org/api/stream.html) ```js const {pipe, abort} = await resumeToPipeableStream(reactNode, postponedState, options?) ``` This API is specific to Node.js. Environments with [Web Streams,](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API) like Deno and modern edge runtimes, should use [`resume`](/reference/react-dom/server/renderToReadableStream) instead. --- ## Reference {/*reference*/} ### `resumeToPipeableStream(node, postponed, options?)` {/*resume-to-pipeable-stream*/} Call `resume` to resume rendering a pre-rendered React tree as HTML into a [Node.js Stream.](https://nodejs.org/api/stream.html#writable-streams) ```js import { resume } from 'react-dom/server'; import {getPostponedState} from './storage'; async function handler(request, response) { const postponed = await getPostponedState(request); const {pipe} = resumeToPipeableStream(, postponed, { onShellReady: () => { pipe(response); } }); } ``` [See more examples below.](#usage) #### Parameters {/*parameters*/} * `reactNode`: The React node you called `prerender` with. For example, a JSX element like ``. It is expected to represent the entire document, so the `App` component should render the `` tag. * `postponedState`: The opaque `postpone` object returned from a [prerender API](/reference/react-dom/static/index), loaded from wherever you stored it (e.g. redis, a file, or S3). * **optional** `options`: An object with streaming options. * **optional** `nonce`: A [`nonce`](http://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#nonce) string to allow scripts for [`script-src` Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src). * **optional** `signal`: An [abort signal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) that lets you [abort server rendering](#aborting-server-rendering) and render the rest on the client. * **optional** `onError`: A callback that fires whenever there is a server error, whether [recoverable](/reference/react-dom/server/renderToReadableStream#recovering-from-errors-outside-the-shell) or [not.](/reference/react-dom/server/renderToReadableStream#recovering-from-errors-inside-the-shell) By default, this only calls `console.error`. If you override it to [log crash reports,](/reference/react-dom/server/renderToReadableStream#logging-crashes-on-the-server) make sure that you still call `console.error`. * **optional** `onShellReady`: A callback that fires right after the [shell](#specifying-what-goes-into-the-shell) has finished. You can call `pipe` here to start streaming. React will [stream the additional content](#streaming-more-content-as-it-loads) after the shell along with the inline ` ``` クライアント側では、ブートストラップスクリプトは [`hydrateRoot` を呼び出して `document` 全体のハイドレーションを行う](/reference/react-dom/client/hydrateRoot#hydrating-an-entire-document)必要があります。 ```js [[1, 4, ""]] import { hydrateRoot } from 'react-dom/client'; import App from './App.js'; hydrateRoot(document, ); ``` これにより、サーバで生成された静的な HTML にイベントリスナが追加され、操作可能になります。 #### ビルド出力から CSS と JS のアセットパスを読み取る {/*reading-css-and-js-asset-paths-from-the-build-output*/} ビルド後に、最終的なアセット URL(JavaScript や CSS ファイルなど)にはよくハッシュ化が行われます。例えば、`styles.css` が `styles.123456.css` になることがあります。静的なアセットのファイル名をハッシュ化することで、同じアセットがビルドごとに異なるファイル名になることが保証されます。これが有用なのは、ある特定の名前を持つファイルの内容が不変になり、静的なアセットの長期的なキャッシングを安全に行えるようになるためです。 しかし、ビルド後までアセット URL が分からない場合、それらをソースコードに含めることができません。例えば、先ほどのように JSX に `"/styles.css"` をハードコーディングする方法は動作しません。ソースコードにそれらを含めないようにするため、ルートコンポーネントが、props 経由で渡されたマップから実際のファイル名を読み取るようにすることができます。 ```js {1,6} export default function App({ assetMap }) { return ( My app ... ); } ``` サーバ上では、`` のようにレンダーし、アセット URL を含む `assetMap` を渡します。 ```js {1-5,8,9} // You'd need to get this JSON from your build tooling, e.g. read it from the build output. const assetMap = { 'styles.css': '/styles.123456.css', 'main.js': '/main.123456.js' }; async function handler(request) { const {prelude} = await prerender(, { bootstrapScripts: [assetMap['/main.js']] }); return new Response(prelude, { headers: { 'content-type': 'text/html' }, }); } ``` サーバで `` のようにレンダーしているので、クライアントでも `assetMap` を使ってレンダーしてハイドレーションエラーを避ける必要があります。このためには以下のように `assetMap` をシリアライズしてクライアントに渡します。 ```js {9-10} // You'd need to get this JSON from your build tooling. const assetMap = { 'styles.css': '/styles.123456.css', 'main.js': '/main.123456.js' }; async function handler(request) { const {prelude} = await prerender(, { // Careful: It's safe to stringify() this because this data isn't user-generated. bootstrapScriptContent: `window.assetMap = ${JSON.stringify(assetMap)};`, bootstrapScripts: [assetMap['/main.js']], }); return new Response(prelude, { headers: { 'content-type': 'text/html' }, }); } ``` 上記の例では、`bootstrapScriptContent` オプションを使って` ``` クライアント側では、ブートストラップスクリプトは [`hydrateRoot` を呼び出して `document` 全体のハイドレーションを行う](/reference/react-dom/client/hydrateRoot#hydrating-an-entire-document)必要があります。 ```js [[1, 4, ""]] import { hydrateRoot } from 'react-dom/client'; import App from './App.js'; hydrateRoot(document, ); ``` これにより、サーバで生成された静的な HTML にイベントリスナが追加され、操作可能になります。 #### ビルド出力から CSS と JS のアセットパスを読み取る {/*reading-css-and-js-asset-paths-from-the-build-output*/} ビルド後に、最終的なアセット URL(JavaScript や CSS ファイルなど)にはよくハッシュ化が行われます。例えば、`styles.css` が `styles.123456.css` になることがあります。静的なアセットのファイル名をハッシュ化することで、同じアセットがビルドごとに異なるファイル名になることが保証されます。これが有用なのは、ある特定の名前を持つファイルの内容が不変になり、静的なアセットの長期的なキャッシングを安全に行えるようになるためです。 しかし、ビルド後までアセット URL が分からない場合、それらをソースコードに含めることができません。例えば、先ほどのように JSX に `"/styles.css"` をハードコーディングする方法は動作しません。ソースコードにそれらを含めないようにするため、ルートコンポーネントが、props 経由で渡されたマップから実際のファイル名を読み取るようにすることができます。 ```js {1,6} export default function App({ assetMap }) { return ( My app ... ); } ``` サーバ上では、`` のようにレンダーし、アセット URL を含む `assetMap` を渡します。 ```js {1-5,8,9} // You'd need to get this JSON from your build tooling, e.g. read it from the build output. const assetMap = { 'styles.css': '/styles.123456.css', 'main.js': '/main.123456.js' }; app.use('/', async (request, response) => { const { prelude } = await prerenderToNodeStream(, { bootstrapScripts: [assetMap['/main.js']] }); response.setHeader('Content-Type', 'text/html'); prelude.pipe(response); }); ``` サーバで `` のようにレンダーしているので、クライアントでも `assetMap` を使ってレンダーしてハイドレーションエラーを避ける必要があります。このためには以下のように `assetMap` をシリアライズしてクライアントに渡します。 ```js {9-10} // You'd need to get this JSON from your build tooling. const assetMap = { 'styles.css': '/styles.123456.css', 'main.js': '/main.123456.js' }; app.use('/', async (request, response) => { const { prelude } = await prerenderToNodeStream(, { // Careful: It's safe to stringify() this because this data isn't user-generated. bootstrapScriptContent: `window.assetMap = ${JSON.stringify(assetMap)};`, bootstrapScripts: [assetMap['/main.js']], }); response.setHeader('Content-Type', 'text/html'); prelude.pipe(response); }); ``` 上記の例では、`bootstrapScriptContent` オプションを使って`