Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: add tabbed terminal component, show npm/bun install #7672

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/components/MDX/MDXComponents.tsx
Original file line number Diff line number Diff line change
@@ -25,6 +25,7 @@ import Diagram from './Diagram';
import DiagramGroup from './DiagramGroup';
import SimpleCallout from './SimpleCallout';
import TerminalBlock from './TerminalBlock';
import TabTerminalBlock from './TabTerminalBlock';
import YouWillLearnCard from './YouWillLearnCard';
import {Challenges, Hint, Solution} from './Challenges';
import {IconNavArrow} from '../Icon/IconNavArrow';
@@ -521,6 +522,7 @@ export const MDXComponents = {
SandpackWithHTMLOutput,
TeamMember,
TerminalBlock,
TabTerminalBlock,
YouWillLearn,
YouWillLearnCard,
Challenges,
245 changes: 245 additions & 0 deletions src/components/MDX/TabTerminalBlock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*/

import * as React from 'react';
import {useState, useEffect, useCallback} from 'react';
import TerminalBlock from './TerminalBlock';
import {IconTerminal} from '../Icon/IconTerminal';

type TabOption = {
label: string;
value: string;
content: string;
};

// Define this outside of any conditionals for SSR compatibility
const STORAGE_KEY = 'react-terminal-tabs';

// Map key for active tab preferences - only used on client
let activeTabsByKey: Record<string, string> = {};
let subscribersByKey: Record<string, Set<(tab: string) => void>> = {};

function saveToLocalStorage() {
if (typeof window !== 'undefined') {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(activeTabsByKey));
} catch (e) {
// Ignore errors
}
}
}

function getSubscribers(key: string): Set<(tab: string) => void> {
if (!subscribersByKey[key]) {
subscribersByKey[key] = new Set();
}
return subscribersByKey[key];
}

function setActiveTab(key: string, tab: string) {
activeTabsByKey[key] = tab;
saveToLocalStorage();

const subscribers = getSubscribers(key);
subscribers.forEach((callback) => callback(tab));
}

function useTabState(
key: string,
defaultTab: string
): [string, (tab: string) => void] {
// Start with the default tab for SSR
const [activeTab, setLocalActiveTab] = useState(defaultTab);
const [initialized, setInitialized] = useState(false);

// Initialize from localStorage after mount
useEffect(() => {
// Read from localStorage
try {
const savedState = localStorage.getItem(STORAGE_KEY);
if (savedState) {
const parsed = JSON.parse(savedState);
if (parsed && typeof parsed === 'object') {
Object.assign(activeTabsByKey, parsed);
}
}
} catch (e) {
// Ignore errors
}

// Set up storage event listener
const handleStorageChange = (e: StorageEvent) => {
if (e.key === STORAGE_KEY && e.newValue) {
try {
const parsed = JSON.parse(e.newValue);
if (parsed && typeof parsed === 'object') {
Object.assign(activeTabsByKey, parsed);

Object.entries(parsed).forEach(([k, value]) => {
const subscribers = subscribersByKey[k];
if (subscribers) {
subscribers.forEach((callback) => callback(value as string));
}
});
}
} catch (e) {
// Ignore errors
}
}
};

window.addEventListener('storage', handleStorageChange);

// Now get the value from localStorage or keep using default
const storedValue = activeTabsByKey[key] || defaultTab;
setLocalActiveTab(storedValue);
setInitialized(true);

// Make sure this key is in our global store
if (!activeTabsByKey[key]) {
activeTabsByKey[key] = defaultTab;
saveToLocalStorage();
}

return () => {
window.removeEventListener('storage', handleStorageChange);
};
}, [key, defaultTab]);

// Set up subscription effect
useEffect(() => {
// Skip if not yet initialized
if (!initialized) return;

const onTabChange = (newTab: string) => {
setLocalActiveTab(newTab);
};

const subscribers = getSubscribers(key);
subscribers.add(onTabChange);

return () => {
subscribers.delete(onTabChange);

if (subscribers.size === 0) {
delete subscribersByKey[key];
}
};
}, [key, initialized]);

// Create a stable setter function
const setTab = useCallback(
(newTab: string) => {
setActiveTab(key, newTab);
},
[key]
);

return [activeTab, setTab];
}

interface TabTerminalBlockProps {
/** Terminal's message level: info, warning, or error */
level?: 'info' | 'warning' | 'error';

/**
* Tab options, each with a label, value, and content.
* Example: [
* { label: 'npm', value: 'npm', content: 'npm install react' },
* { label: 'Bun', value: 'bun', content: 'bun install react' }
* ]
*/
tabs?: Array<TabOption>;

/** Optional initial active tab value */
defaultTab?: string;

/**
* Optional storage key for tab state.
* All TabTerminalBlocks with the same key will share tab selection.
*/
storageKey?: string;
}

/**
* TabTerminalBlock displays a terminal block with tabs.
* Tabs sync across instances with the same storageKey.
*
* @example
* <TabTerminalBlock
* tabs={[
* { label: 'npm', value: 'npm', content: 'npm install react' },
* { label: 'Bun', value: 'bun', content: 'bun install react' }
* ]}
* />
*/
function TabTerminalBlock({
level = 'info',
tabs = [],
defaultTab,
storageKey = 'package-manager',
}: TabTerminalBlockProps) {
// Create a fallback tab if none provided
const safeTabsList =
tabs && tabs.length > 0
? tabs
: [{label: 'Terminal', value: 'default', content: 'No content provided'}];

// Always use the first tab as initial defaultTab for SSR consistency
// This ensures server and client render the same content initially
const initialDefaultTab = defaultTab || safeTabsList[0].value;

// Set up tab state
const [activeTab, setTabValue] = useTabState(storageKey, initialDefaultTab);

const handleTabClick = useCallback(
(tabValue: string) => {
return () => setTabValue(tabValue);
},
[setTabValue]
);

// Handle the case with no content - after hooks have been called
if (
safeTabsList.length === 0 ||
safeTabsList[0].content === 'No content provided'
) {
return (
<TerminalBlock level="error">
Error: No tab content provided
</TerminalBlock>
);
}

const activeTabOption =
safeTabsList.find((tab) => tab.value === activeTab) || safeTabsList[0];

const customHeader = (
<div className="flex items-center">
<IconTerminal className="mr-3" />
<div className="flex items-center">
{safeTabsList.map((tab) => (
<button
key={tab.value}
className={`text-sm font-medium px-3 py-1 h-7 mx-0.5 inline-flex items-center justify-center rounded-sm transition-colors ${
activeTab === tab.value
? 'bg-gray-50/50 text-primary dark:bg-gray-800/30 dark:text-primary-dark'
: 'text-primary dark:text-primary-dark hover:bg-gray-50/30 dark:hover:bg-gray-800/20'
}`}
onClick={handleTabClick(tab.value)}>
{tab.label}
</button>
))}
</div>
</div>
);

return (
<TerminalBlock level={level} customHeader={customHeader}>
{activeTabOption.content}
</TerminalBlock>
);
}

export default TabTerminalBlock;
18 changes: 14 additions & 4 deletions src/components/MDX/TerminalBlock.tsx
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@ type LogLevel = 'info' | 'warning' | 'error';
interface TerminalBlockProps {
level?: LogLevel;
children: React.ReactNode;
customHeader?: React.ReactNode;
}

function LevelText({type}: {type: LogLevel}) {
@@ -25,7 +26,11 @@ function LevelText({type}: {type: LogLevel}) {
}
}

function TerminalBlock({level = 'info', children}: TerminalBlockProps) {
function TerminalBlock({
level = 'info',
children,
customHeader,
}: TerminalBlockProps) {
let message: string | undefined;
if (typeof children === 'string') {
message = children;
@@ -53,15 +58,20 @@ function TerminalBlock({level = 'info', children}: TerminalBlockProps) {
}, [copied]);

return (
<div className="rounded-lg bg-secondary dark:bg-gray-50 h-full">
<div className="rounded-lg bg-secondary dark:bg-gray-50 h-full my-4">
<div className="bg-gray-90 dark:bg-gray-60 w-full rounded-t-lg">
<div className="text-primary-dark dark:text-primary-dark flex text-sm px-4 py-0.5 relative justify-between">
<div>
<IconTerminal className="inline-flex me-2 self-center" /> Terminal
{customHeader || (
<>
<IconTerminal className="inline-flex me-2 self-center" />{' '}
Terminal
</>
)}
</div>
<div>
<button
className="w-full text-start text-primary-dark dark:text-primary-dark "
className="w-full text-start text-primary-dark dark:text-primary-dark"
onClick={() => {
window.navigator.clipboard.writeText(message ?? '');
setCopied(true);
9 changes: 6 additions & 3 deletions src/content/blog/2021/12/17/react-conf-2021-recap.md
Original file line number Diff line number Diff line change
@@ -43,9 +43,12 @@ In the keynote, we also announced that the React 18 RC is available to try now.

To try the React 18 RC, upgrade your dependencies:

```bash
npm install react@rc react-dom@rc
```
<TabTerminalBlock
tabs={[
{ label: 'npm', value: 'npm', content: 'npm install react@rc react-dom@rc' },
{ label: 'Bun', value: 'bun', content: 'bun add react@rc react-dom@rc' }
]}
/>

and switch to the new `createRoot` API:

16 changes: 7 additions & 9 deletions src/content/blog/2022/03/08/react-18-upgrade-guide.md
Original file line number Diff line number Diff line change
@@ -29,15 +29,13 @@ For React Native users, React 18 will ship in a future version of React Native.

To install the latest version of React:

```bash
npm install react react-dom
```

Or if you’re using yarn:

```bash
yarn add react react-dom
```
<TabTerminalBlock
tabs={[
{ label: 'npm', value: 'npm', content: 'npm install react react-dom' },
{ label: 'yarn', value: 'yarn', content: 'yarn add react react-dom' },
{ label: 'Bun', value: 'bun', content: 'bun add react react-dom' }
]}
/>

## Updates to Client Rendering APIs {/*updates-to-client-rendering-apis*/}

121 changes: 75 additions & 46 deletions src/content/blog/2024/04/25/react-19-upgrade-guide.md
Original file line number Diff line number Diff line change
@@ -69,25 +69,23 @@ We expect most apps will not be affected since the transform is enabled in most

To install the latest version of React and React DOM:

```bash
npm install --save-exact react@^19.0.0 react-dom@^19.0.0
```

Or, if you're using Yarn:

```bash
yarn add --exact react@^19.0.0 react-dom@^19.0.0
```
<TabTerminalBlock
tabs={[
{ label: 'npm', value: 'npm', content: 'npm install --save-exact react@^19.0.0 react-dom@^19.0.0' },
{ label: 'yarn', value: 'yarn', content: 'yarn add --exact react@^19.0.0 react-dom@^19.0.0' },
{ label: 'Bun', value: 'bun', content: 'bun add --exact react@^19.0.0 react-dom@^19.0.0' }
]}
/>

If you're using TypeScript, you also need to update the types.
```bash
npm install --save-exact @types/react@^19.0.0 @types/react-dom@^19.0.0
```

Or, if you're using Yarn:
```bash
yarn add --exact @types/react@^19.0.0 @types/react-dom@^19.0.0
```
<TabTerminalBlock
tabs={[
{ label: 'npm', value: 'npm', content: 'npm install --save-exact @types/react@^19.0.0 @types/react-dom@^19.0.0' },
{ label: 'yarn', value: 'yarn', content: 'yarn add --exact @types/react@^19.0.0 @types/react-dom@^19.0.0' },
{ label: 'Bun', value: 'bun', content: 'bun add --exact @types/react@^19.0.0 @types/react-dom@^19.0.0' }
]}
/>

We're also including a codemod for the most common replacements. See [TypeScript changes](#typescript-changes) below.

@@ -104,9 +102,12 @@ All codemods are available in the [`react-codemod` repo](https://github.com/reac

Run all codemods listed in this guide with the React 19 `codemod` recipe:

```bash
npx codemod@latest react/19/migration-recipe
```
<TabTerminalBlock
tabs={[
{ label: 'npm', value: 'npm', content: 'npx codemod@latest react/19/migration-recipe' },
{ label: 'Bun', value: 'bun', content: 'bunx codemod@latest react/19/migration-recipe' }
]}
/>

This will run the following codemods from `react-codemod`:
- [`replace-reactdom-render`](https://github.com/reactjs/react-codemod?tab=readme-ov-file#replace-reactdom-render)
@@ -187,9 +188,12 @@ function Heading({text = 'Hello, world!'}: Props) {

Codemod `propTypes` to TypeScript with:

```bash
npx codemod@latest react/prop-types-typescript
```
<TabTerminalBlock
tabs={[
{ label: 'npm', value: 'npm', content: 'npx codemod@latest react/prop-types-typescript' },
{ label: 'Bun', value: 'bun', content: 'bunx codemod@latest react/prop-types-typescript' }
]}
/>

</Note>

@@ -290,9 +294,12 @@ class MyComponent extends React.Component {

Codemod string refs with `ref` callbacks:

```bash
npx codemod@latest react/19/replace-string-ref
```
<TabTerminalBlock
tabs={[
{ label: 'npm', value: 'npm', content: 'npx codemod@latest react/19/replace-string-ref' },
{ label: 'Bun', value: 'bun', content: 'bunx codemod@latest react/19/replace-string-ref' }
]}
/>

</Note>

@@ -336,9 +343,13 @@ const button = <button />;

In React 18, we updated `react-test-renderer/shallow` to re-export [react-shallow-renderer](https://github.com/enzymejs/react-shallow-renderer). In React 19, we're removing `react-test-render/shallow` to prefer installing the package directly:

```bash
npm install react-shallow-renderer --save-dev
```
<TabTerminalBlock
tabs={[
{ label: 'npm', value: 'npm', content: 'npm install react-shallow-renderer --save-dev' },
{ label: 'Bun', value: 'bun', content: 'bun add react-shallow-renderer --dev' }
]}
/>

```diff
- import ShallowRenderer from 'react-test-renderer/shallow';
+ import ShallowRenderer from 'react-shallow-renderer';
@@ -383,9 +394,12 @@ See the [warning page](https://react.dev/warnings/react-dom-test-utils) for alte

Codemod `ReactDOMTestUtils.act` to `React.act`:

```bash
npx codemod@latest react/19/replace-act-import
```
<TabTerminalBlock
tabs={[
{ label: 'npm', value: 'npm', content: 'npx codemod@latest react/19/replace-act-import' },
{ label: 'Bun', value: 'bun', content: 'bunx codemod@latest react/19/replace-act-import' }
]}
/>

</Note>

@@ -408,9 +422,12 @@ root.render(<App />);

Codemod `ReactDOM.render` to `ReactDOMClient.createRoot`:

```bash
npx codemod@latest react/19/replace-reactdom-render
```
<TabTerminalBlock
tabs={[
{ label: 'npm', value: 'npm', content: 'npx codemod@latest react/19/replace-reactdom-render' },
{ label: 'Bun', value: 'bun', content: 'bunx codemod@latest react/19/replace-reactdom-render' }
]}
/>

</Note>

@@ -432,9 +449,12 @@ hydrateRoot(document.getElementById('root'), <App />);

Codemod `ReactDOM.hydrate` to `ReactDOMClient.hydrateRoot`:

```bash
npx codemod@latest react/19/replace-reactdom-render
```
<TabTerminalBlock
tabs={[
{ label: 'npm', value: 'npm', content: 'npx codemod@latest react/19/replace-reactdom-render' },
{ label: 'Bun', value: 'bun', content: 'bunx codemod@latest react/19/replace-reactdom-render' }
]}
/>

</Note>

@@ -457,9 +477,12 @@ For more see `root.unmount()` for [`createRoot`](https://react.dev/reference/rea

Codemod `unmountComponentAtNode` to `root.unmount`:

```bash
npx codemod@latest react/19/replace-reactdom-render
```
<TabTerminalBlock
tabs={[
{ label: 'npm', value: 'npm', content: 'npx codemod@latest react/19/replace-reactdom-render' },
{ label: 'Bun', value: 'bun', content: 'bunx codemod@latest react/19/replace-reactdom-render' }
]}
/>

</Note>

@@ -584,15 +607,21 @@ We've cleaned up the TypeScript types based on the removed APIs in React 19. Som
<Note>
We've published [`types-react-codemod`](https://github.com/eps1lon/types-react-codemod/) to migrate most type related breaking changes:

```bash
npx types-react-codemod@latest preset-19 ./path-to-app
```
<TabTerminalBlock
tabs={[
{ label: 'npm', value: 'npm', content: 'npx types-react-codemod@latest preset-19 ./path-to-app' },
{ label: 'Bun', value: 'bun', content: 'bunx types-react-codemod@latest preset-19 ./path-to-app' }
]}
/>

If you have a lot of unsound access to `element.props`, you can run this additional codemod:

```bash
npx types-react-codemod@latest react-element-default-any-props ./path-to-your-react-ts-files
```
<TabTerminalBlock
tabs={[
{ label: 'npm', value: 'npm', content: 'npx types-react-codemod@latest react-element-default-any-props ./path-to-your-react-ts-files' },
{ label: 'Bun', value: 'bun', content: 'bunx types-react-codemod@latest react-element-default-any-props ./path-to-your-react-ts-files' }
]}
/>

</Note>

32 changes: 14 additions & 18 deletions src/content/blog/2024/10/21/react-compiler-beta-release.md
Original file line number Diff line number Diff line change
@@ -34,15 +34,13 @@ At [React India 2024](https://www.youtube.com/watch?v=qd5yk2gxbtg), we shared an

To install React Compiler Beta:

<TerminalBlock>
npm install -D babel-plugin-react-compiler@beta eslint-plugin-react-compiler@beta
</TerminalBlock>

Or, if you're using Yarn:

<TerminalBlock>
yarn add -D babel-plugin-react-compiler@beta eslint-plugin-react-compiler@beta
</TerminalBlock>
<TabTerminalBlock
tabs={[
{ label: 'npm', value: 'npm', content: 'npm install -D babel-plugin-react-compiler@beta eslint-plugin-react-compiler@beta' },
{ label: 'yarn', value: 'yarn', content: 'yarn add -D babel-plugin-react-compiler@beta eslint-plugin-react-compiler@beta' },
{ label: 'Bun', value: 'bun', content: 'bun add -D babel-plugin-react-compiler@beta eslint-plugin-react-compiler@beta' }
]}
/>

You can watch [Sathya Gunasekaran's](https://twitter.com/_gsathya) talk at React India here:

@@ -54,15 +52,13 @@ React Compiler’s ESLint plugin helps developers proactively identify and corre

To install the linter only:

<TerminalBlock>
npm install -D eslint-plugin-react-compiler@beta
</TerminalBlock>

Or, if you're using Yarn:

<TerminalBlock>
yarn add -D eslint-plugin-react-compiler@beta
</TerminalBlock>
<TabTerminalBlock
tabs={[
{ label: 'npm', value: 'npm', content: 'npm install -D eslint-plugin-react-compiler@beta' },
{ label: 'yarn', value: 'yarn', content: 'yarn add -D eslint-plugin-react-compiler@beta' },
{ label: 'Bun', value: 'bun', content: 'bun add -D eslint-plugin-react-compiler@beta' }
]}
/>

After installation you can enable the linter by [adding it to your ESLint config](/learn/react-compiler#installing-eslint-plugin-react-compiler). Using the linter helps identify Rules of React breakages, making it easier to adopt the compiler when it's fully released.

11 changes: 7 additions & 4 deletions src/content/learn/add-react-to-an-existing-project.md
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ If you want to add some interactivity to your existing project, you don't have t

<Note>

**You need to install [Node.js](https://nodejs.org/en/) for local development.** Although you can [try React](/learn/installation#try-react) online or with a simple HTML page, realistically most JavaScript tooling you'll want to use for development requires Node.js.
**You need to install [Node.js](https://nodejs.org/en/) or [Bun](https://bun.sh/) for local development.** Although you can [try React](/learn/installation#try-react) online or with a simple HTML page, realistically most JavaScript tooling you'll want to use for development requires Node.js or Bun.

</Note>

@@ -49,9 +49,12 @@ A modular JavaScript environment lets you write your React components in individ

To check whether your setup works, run this command in your project folder:

<TerminalBlock>
npm install react react-dom
</TerminalBlock>
<TabTerminalBlock
tabs={[
{ label: 'npm', value: 'npm', content: 'npm install react react-dom' },
{ label: 'Bun', value: 'bun', content: 'bun install react react-dom' }
]}
/>

Then add these lines of code at the top of your main JavaScript file (it might be called `index.js` or `main.js`):

27 changes: 18 additions & 9 deletions src/content/learn/build-a-react-app-from-scratch.md
Original file line number Diff line number Diff line change
@@ -33,9 +33,12 @@ The first step is to install a build tool like `vite`, `parcel`, or `rsbuild`. T

[Vite](https://vite.dev/) is a build tool that aims to provide a faster and leaner development experience for modern web projects.

<TerminalBlock>
{`npm create vite@latest my-app -- --template react`}
</TerminalBlock>
<TabTerminalBlock
tabs={[
{ label: 'npm', value: 'npm', content: 'npm create vite@latest my-app -- --template react' },
{ label: 'Bun', value: 'bun', content: 'bun create vite@latest my-app -- --template react' }
]}
/>

Vite is opinionated and comes with sensible defaults out of the box. Vite has a rich ecosystem of plugins to support fast refresh, JSX, Babel/SWC, and other common features. See Vite's [React plugin](https://vite.dev/plugins/#vitejs-plugin-react) or [React SWC plugin](https://vite.dev/plugins/#vitejs-plugin-react-swc) and [React SSR example project](https://vite.dev/guide/ssr.html#example-projects) to get started.

@@ -45,19 +48,25 @@ Vite is already being used as a build tool in one of our [recommended frameworks

[Parcel](https://parceljs.org/) combines a great out-of-the-box development experience with a scalable architecture that can take your project from just getting started to massive production applications.

<TerminalBlock>
{`npm install --save-dev parcel`}
</TerminalBlock>
<TabTerminalBlock
tabs={[
{ label: 'npm', value: 'npm', content: 'npm install --save-dev parcel' },
{ label: 'Bun', value: 'bun', content: 'bun add --dev parcel' }
]}
/>

Parcel supports fast refresh, JSX, TypeScript, Flow, and styling out of the box. See [Parcel's React recipe](https://parceljs.org/recipes/react/#getting-started) to get started.

### Rsbuild {/*rsbuild*/}

[Rsbuild](https://rsbuild.dev/) is an Rspack-powered build tool that provides a seamless development experience for React applications. It comes with carefully tuned defaults and performance optimizations ready to use.

<TerminalBlock>
{`npx create-rsbuild --template react`}
</TerminalBlock>
<TabTerminalBlock
tabs={[
{ label: 'npm', value: 'npm', content: 'npx create-rsbuild --template react' },
{ label: 'Bun', value: 'bun', content: 'bunx create-rsbuild --template react' }
]}
/>

Rsbuild includes built-in support for React features like fast refresh, JSX, TypeScript, and styling. See [Rsbuild's React guide](https://rsbuild.dev/guide/framework/react) to get started.

27 changes: 18 additions & 9 deletions src/content/learn/creating-a-react-app.md
Original file line number Diff line number Diff line change
@@ -28,9 +28,12 @@ This allows you to start with a client-only app, and if your needs change later,

**[Next.js's App Router](https://nextjs.org/docs) is a React framework that takes full advantage of React's architecture to enable full-stack React apps.**

<TerminalBlock>
npx create-next-app@latest
</TerminalBlock>
<TabTerminalBlock
tabs={[
{ label: 'npm', value: 'npm', content: 'npx create-next-app@latest' },
{ label: 'Bun', value: 'bun', content: 'bunx create-next-app@latest' }
]}
/>

Next.js is maintained by [Vercel](https://vercel.com/). You can [deploy a Next.js app](https://nextjs.org/docs/app/building-your-application/deploying) to any Node.js or serverless hosting, or to your own server. Next.js also supports [static export](https://nextjs.org/docs/app/building-your-application/deploying/static-exports) which doesn't require a server. Vercel additionally provides opt-in paid cloud services.

@@ -40,19 +43,25 @@ Next.js is maintained by [Vercel](https://vercel.com/). You can [deploy a Next.j

To create a new React Router framework project, run:

<TerminalBlock>
npx create-react-router@latest
</TerminalBlock>
<TabTerminalBlock
tabs={[
{ label: 'npm', value: 'npm', content: 'npx create-react-router@latest' },
{ label: 'Bun', value: 'bun', content: 'bunx create-react-router@latest' }
]}
/>

React Router is maintained by [Shopify](https://www.shopify.com).

### Expo (for native apps) {/*expo*/}

**[Expo](https://expo.dev/) is a React framework that lets you create universal Android, iOS, and web apps with truly native UIs.** It provides an SDK for [React Native](https://reactnative.dev/) that makes the native parts easier to use. To create a new Expo project, run:

<TerminalBlock>
npx create-expo-app@latest
</TerminalBlock>
<TabTerminalBlock
tabs={[
{ label: 'npm', value: 'npm', content: 'npx create-expo-app@latest' },
{ label: 'Bun', value: 'bun', content: 'bunx create-expo-app@latest' }
]}
/>

If you're new to Expo, check out the [Expo tutorial](https://docs.expo.dev/tutorial/introduction/).

51 changes: 30 additions & 21 deletions src/content/learn/react-compiler.md
Original file line number Diff line number Diff line change
@@ -30,15 +30,12 @@ The compiler also includes an [ESLint plugin](#installing-eslint-plugin-react-co

The compiler is currently released as `beta`, and is available to try out on React 17+ apps and libraries. To install the Beta:

<TerminalBlock>
npm install -D babel-plugin-react-compiler@beta eslint-plugin-react-compiler@beta
</TerminalBlock>

Or, if you're using Yarn:

<TerminalBlock>
yarn add -D babel-plugin-react-compiler@beta eslint-plugin-react-compiler@beta
</TerminalBlock>
<TabTerminalBlock
tabs={[
{ label: 'npm', value: 'npm', content: 'npm install -D babel-plugin-react-compiler@beta eslint-plugin-react-compiler@beta' },
{ label: 'Bun', value: 'bun', content: 'bun add -D babel-plugin-react-compiler@beta eslint-plugin-react-compiler@beta' }
]}
/>

If you are not using React 19 yet, please see [the section below](#using-react-compiler-with-react-17-or-18) for further instructions.

@@ -128,9 +125,12 @@ In addition to these docs, we recommend checking the [React Compiler Working Gro

React Compiler also powers an ESLint plugin. The ESLint plugin can be used **independently** of the compiler, meaning you can use the ESLint plugin even if you don't use the compiler.

<TerminalBlock>
npm install -D eslint-plugin-react-compiler@beta
</TerminalBlock>
<TabTerminalBlock
tabs={[
{ label: 'npm', value: 'npm', content: 'npm install -D eslint-plugin-react-compiler@beta' },
{ label: 'Bun', value: 'bun', content: 'bun add -D eslint-plugin-react-compiler@beta' }
]}
/>

Then, add it to your ESLint config:

@@ -193,9 +193,12 @@ If you're starting a new project, you can enable the compiler on your entire cod

React Compiler works best with React 19 RC. If you are unable to upgrade, you can install the extra `react-compiler-runtime` package which will allow the compiled code to run on versions prior to 19. However, note that the minimum supported version is 17.

<TerminalBlock>
npm install react-compiler-runtime@beta
</TerminalBlock>
<TabTerminalBlock
tabs={[
{ label: 'npm', value: 'npm', content: 'npm install react-compiler-runtime@beta' },
{ label: 'Bun', value: 'bun', content: 'bun add react-compiler-runtime@beta' }
]}
/>

You should also add the correct `target` to your compiler config, where `target` is the major version of React you are targeting:

@@ -228,9 +231,12 @@ Similarly to apps, it is not necessary to fully compile 100% of your components

### Babel {/*usage-with-babel*/}

<TerminalBlock>
npm install babel-plugin-react-compiler@beta
</TerminalBlock>
<TabTerminalBlock
tabs={[
{ label: 'npm', value: 'npm', content: 'npm install babel-plugin-react-compiler@beta' },
{ label: 'Bun', value: 'bun', content: 'bun add babel-plugin-react-compiler@beta' }
]}
/>

The compiler includes a Babel plugin which you can use in your build pipeline to run the compiler.

@@ -283,9 +289,12 @@ Please refer to the [Next.js docs](https://nextjs.org/docs/app/api-reference/nex
### Remix {/*usage-with-remix*/}
Install `vite-plugin-babel`, and add the compiler's Babel plugin to it:

<TerminalBlock>
npm install vite-plugin-babel
</TerminalBlock>
<TabTerminalBlock
tabs={[
{ label: 'npm', value: 'npm', content: 'npm install vite-plugin-babel' },
{ label: 'Bun', value: 'bun', content: 'bun install vite-plugin-babel' }
]}
/>

```js {2,14}
// vite.config.js
9 changes: 6 additions & 3 deletions src/content/learn/typescript.md
Original file line number Diff line number Diff line change
@@ -31,9 +31,12 @@ All [production-grade React frameworks](/learn/start-a-new-react-project#product

To install the latest version of React's type definitions:

<TerminalBlock>
npm install @types/react @types/react-dom
</TerminalBlock>
<TabTerminalBlock
tabs={[
{ label: 'npm', value: 'npm', content: 'npm install @types/react @types/react-dom' },
{ label: 'Bun', value: 'bun', content: 'bun add @types/react @types/react-dom' }
]}
/>

The following compiler options need to be set in your `tsconfig.json`:

11 changes: 6 additions & 5 deletions src/content/warnings/invalid-hook-call-warning.md
Original file line number Diff line number Diff line change
@@ -123,11 +123,12 @@ If these `react` imports resolve to two different exports objects, you will see

If you use Node for package management, you can run this check in your project folder:

<TerminalBlock>

npm ls react

</TerminalBlock>
<TabTerminalBlock
tabs={[
{ label: 'npm', value: 'npm', content: 'npm ls react' },
{ label: 'Bun', value: 'bun', content: 'bun pm ls react' }
]}
/>

If you see more than one React, you'll need to figure out why this happens and fix your dependency tree. For example, maybe a library you're using incorrectly specifies `react` as a dependency (rather than a peer dependency). Until that library is fixed, [Yarn resolutions](https://yarnpkg.com/lang/en/docs/selective-version-resolutions/) is one possible workaround.