Skip to content

Commit

Permalink
feat: react 19 (#5621)
Browse files Browse the repository at this point in the history
* feat(shadcn): add flag prompt for npm

* docs: add docs for react 19

* chore: add changeset

* test: update snapshots

* docs: add notes for recharts

* docs: fix

* fix

* fix: linting
  • Loading branch information
shadcn authored Oct 29, 2024
1 parent f0cff7e commit 64739f8
Show file tree
Hide file tree
Showing 10 changed files with 257 additions and 16 deletions.
5 changes: 5 additions & 0 deletions .changeset/breezy-garlics-whisper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"shadcn": patch
---

add flag prompt for npm
17 changes: 9 additions & 8 deletions apps/www/components/callout.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { cn } from "@/lib/utils"
import {
Alert,
AlertDescription,
AlertTitle,
} from "@/registry/new-york/ui/alert"

interface CalloutProps {
icon?: string
title?: string
children?: React.ReactNode
}

export function Callout({ title, children, icon, ...props }: CalloutProps) {
export function Callout({
title,
children,
icon,
className,
...props
}: React.ComponentProps<typeof Alert> & { icon?: string }) {
return (
<Alert {...props}>
<Alert className={cn("bg-muted/50", className)} {...props}>
{icon && <span className="mr-4 text-2xl">{icon}</span>}
{title && <AlertTitle>{title}</AlertTitle>}
<AlertDescription>{children}</AlertDescription>
Expand Down
13 changes: 8 additions & 5 deletions apps/www/components/mdx-components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,23 +139,26 @@ const components = {
<hr className="my-4 md:my-8" {...props} />
),
table: ({ className, ...props }: React.HTMLAttributes<HTMLTableElement>) => (
<div className="my-6 w-full overflow-y-auto rounded-lg">
<div className="my-6 w-full overflow-y-auto">
<table
className={cn(
"relative w-full overflow-hidden text-sm after:absolute after:inset-0 after:rounded-lg after:ring-1 after:ring-border",
"relative w-full overflow-hidden border-none text-sm",
className
)}
{...props}
/>
</div>
),
tr: ({ className, ...props }: React.HTMLAttributes<HTMLTableRowElement>) => (
<tr className={cn("m-0 border-t", className)} {...props} />
<tr
className={cn("last:border-b-none m-0 border-b", className)}
{...props}
/>
),
th: ({ className, ...props }: React.HTMLAttributes<HTMLTableCellElement>) => (
<th
className={cn(
"border px-4 py-2 text-left font-bold [&[align=center]]:text-center [&[align=right]]:text-right",
"px-4 py-2 text-left font-bold [&[align=center]]:text-center [&[align=right]]:text-right",
className
)}
{...props}
Expand All @@ -164,7 +167,7 @@ const components = {
td: ({ className, ...props }: React.HTMLAttributes<HTMLTableCellElement>) => (
<td
className={cn(
"border px-4 py-2 text-left [&[align=center]]:text-center [&[align=right]]:text-right",
"px-4 py-2 text-left [&[align=center]]:text-center [&[align=right]]:text-right",
className
)}
{...props}
Expand Down
5 changes: 5 additions & 0 deletions apps/www/config/docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ export const docsConfig: DocsConfig = {
href: "/docs/cli",
items: [],
},
{
title: "Next.js 15 + React 19",
href: "/docs/react-19",
items: [],
},
{
title: "Typography",
href: "/docs/components/typography",
Expand Down
8 changes: 7 additions & 1 deletion apps/www/content/docs/components/chart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ description: Beautiful charts. Built using Recharts. Copy and paste into your ap
component: true
---

<Callout className="mb-6">

**Note:** If you are using charts with **React 19** or the **Next.js 15**, see the note [here](/docs/react-19#recharts).

</Callout>

<ComponentPreview
name="chart-bar-interactive"
className="-mt-4 [&_.preview]:p-0 [&_.preview]:border-t [&_.preview>div]:shadow-none [&_.preview>div]:border-none [&_.preview>div]:w-full [&_.preview]:lg:min-h-[404px]"
Expand Down Expand Up @@ -47,7 +53,7 @@ We do not wrap Recharts. This means you're not locked into an abstraction. When

<Callout className="mt-4">

**Note:** If you are trying to use charts with **React 19** or the **Next.js 15**, you will need the [recharts@alpha](https://github.com/recharts/recharts/releases/tag/v2.13.0-alpha.4) release currently.
**Note:** If you are using charts with **React 19** or the **Next.js 15**, see the note [here](/docs/react-19#recharts).

</Callout>

Expand Down
6 changes: 6 additions & 0 deletions apps/www/content/docs/installation/next.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ title: Next.js
description: Install and configure Next.js.
---

<Callout>

**If you're using Next.js 15, see the [Next.js 15 + React 19](/docs/installation/react-19) guide.**

</Callout>

<Steps>

### Create project
Expand Down
166 changes: 166 additions & 0 deletions apps/www/content/docs/react-19.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
---
title: Next.js 15 + React 19
description: Using shadcn/ui with Next.js 15 and React 19.
---

<Callout>
**The following guide applies to any framework that supports React 19**. I
titled this page "Next.js 15 + React 19" to help people upgrading to Next.js
15 find it. We are working with package maintainers to help upgrade to React
19.
</Callout>

## TL;DR

If you're using `npm`, you can install shadcn/ui dependencies with a flag. The `shadcn` CLI will prompt you to select a flag when you run it. No flags required for pnpm, bun, or yarn.

See [Upgrade Status](#upgrade-status) for the status of React 19 support for each package.

## What's happening?

React 19 is now [rc](https://www.npmjs.com/package/react?activeTab=versions) and is [tested and supported in the latest Next.js 15 release](https://nextjs.org/blog/next-15#react-19).

To support React 19, package maintainers will need to test and update their packages to include React 19 as a peer dependency. This is [already](https://github.com/radix-ui/primitives/pull/2952) [in](https://github.com/pacocoursey/cmdk/pull/318) [progress](https://github.com/emilkowalski/vaul/pull/498).

```diff /^19.0.0/
"peerDependencies": {
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0"
},
```

<Callout>
You can check if a package lists React 19 as a peer dependency by running
`npm info <package> peerDependencies`.
</Callout>

In the meantime, if you are installing a package that **does not** list React 19 as a peer dependency, you will see an error message like this:

```bash
npm error code ERESOLVE
npm error ERESOLVE unable to resolve dependency tree
npm error
npm error While resolving: my-app@0.1.0
npm error Found: react@19.0.0-rc-69d4b800-20241021
npm error node_modules/react
npm error react@"19.0.0-rc-69d4b800-20241021" from the root project
```

<Callout>
**Note:** This is npm only. PNPM and Bun will only show a silent warning.
</Callout>

## How to fix this

### Solution 1: `--force` or `--legacy-peer-deps`

You can force install a package with the `--force` or the `--legacy-peer-deps` flag.

```bash
npm i <package> --force

npm i <package> --legacy-peer-deps
```

This will install the package and ignore the peer dependency warnings.

<Accordion type="multiple">
<AccordionItem value="flags">
<AccordionTrigger className="font-medium">
What do the `--force` and `--legacy-peer-deps` flag do?
</AccordionTrigger>
<AccordionContent className="[&_ul]:mt-0">

- `--force`: Ignores and overrides any dependency conflicts, forcing the
installation of packages.
- `--legacy-peer-deps`: Skips strict peer dependency checks, allowing
installation of packages with unmet peer dependencies to avoid errors.

</AccordionContent>

</AccordionItem>
</Accordion>

### Solution 2: Use React 18

You can downgrade `react` and `react-dom` to version 18, which is compatible with the package you are installing and upgrade when the dependency is updated.

```bash
npm i react@18 react-dom@18
```

Whichever solution you choose, make sure you test your app thoroughly to ensure
there are no regressions.

## Using shadcn/ui on Next.js 15

### Using pnpm, bun, or yarn

Follow the instructions in the [installation guide](/docs/installation/next) to install shadcn/ui. No flags are needed.

### Using npm

When you run `npx shadcn@latest init -d`, you will be prompted to select an option to resolve the peer dependency issues.

```bash
It looks like you are using React 19.
Some packages may fail to install due to peer dependency issues (see https://ui.shadcn.com/react-19).

? How would you like to proceed? › - Use arrow-keys. Return to submit.
❯ Use --force
Use --legacy-peer-deps
```

You can then run the command with the flag you choose.

## Adding components

The process for adding components is the same as above. Select a flag to resolve the peer dependency issues.

**Remember to always test your app after installing new dependencies.**

## Upgrade Status

To make it easy for you track the progress of the upgrade, I've created a table below with React 19 support status for the shadcn/ui dependencies.

- ✅ - Works with React 19 using npm, pnpm, and bun.
- 🚧 - Works with React 19 using pnpm and bun. Requires flag for npm. PR is in progress.

| Package | Status | Note |
| ---------------------------------------------------------------------------------- | ------ | ----------------------------------------------------------- |
| [radix-ui](https://www.npmjs.com/package/@radix-ui/react-icons) || |
| [lucide-react](https://www.npmjs.com/package/lucide-react) || |
| [class-variance-authority](https://www.npmjs.com/package/class-variance-authority) || Does not list React 19 as a peer dependency. |
| [tailwindcss-animate](https://www.npmjs.com/package/tailwindcss-animate) || Does not list React 19 as a peer dependency. |
| [embla-carousel-react](https://www.npmjs.com/package/embla-carousel-react) || |
| [recharts](https://www.npmjs.com/package/recharts) || See note [below](#recharts) |
| [react-hook-form](https://www.npmjs.com/package/react-hook-form) || |
| [react-resizable-panels](https://www.npmjs.com/package/react-resizable-panels) || |
| [sonner](https://www.npmjs.com/package/sonner) || |
| [react-day-picker](https://www.npmjs.com/package/react-day-picker) || Works with flag for npm. Work to upgrade to v9 in progress. |
| [input-otp](https://www.npmjs.com/package/input-otp) || |
| [vaul](https://www.npmjs.com/package/vaul) || |
| [@radix-ui/react-icons](https://www.npmjs.com/package/@radix-ui/react-icons) | 🚧 | See [PR #184](https://github.com/radix-ui/icons/pull/184) |
| [cmdk](https://www.npmjs.com/package/cmdk) | 🚧 | See [PR #318](https://github.com/pacocoursey/cmdk/pull/318) |

If you have any questions, please [open an issue](https://github.com/shadcn/ui/issues) on GitHub.

## Recharts

To use recharts with React 19, you will need to override the `react-is` dependency.

<Steps>

<Step>Add the following to your `package.json`</Step>

```json title="package.json"
"overrides": {
"react-is": "^19.0.0-rc-69d4b800-20241021"
}
```

Note: the `react-is` version needs to match the version of React 19 you are using. The above is an example.

<Step>Run `npm install --legacy-peer-deps`</Step>

</Steps>
5 changes: 5 additions & 0 deletions apps/www/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ const nextConfig = {
destination: "/docs/components/sidebar",
permanent: true,
},
{
source: "/react-19",
destination: "/docs/react-19",
permanent: true,
},
]
},
}
Expand Down
46 changes: 45 additions & 1 deletion packages/shadcn/src/utils/updaters/update-dependencies.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { Config } from "@/src/utils/get-config"
import { getPackageInfo } from "@/src/utils/get-package-info"
import { getPackageManager } from "@/src/utils/get-package-manager"
import { logger } from "@/src/utils/logger"
import { RegistryItem } from "@/src/utils/registry/schema"
import { spinner } from "@/src/utils/spinner"
import { execa } from "execa"
import prompts from "prompts"

export async function updateDependencies(
dependencies: RegistryItem["dependencies"],
Expand All @@ -25,12 +28,53 @@ export async function updateDependencies(
silent: options.silent,
})?.start()
const packageManager = await getPackageManager(config.resolvedPaths.cwd)

// Offer to use --force or --legacy-peer-deps if using React 19 with npm.
let flag = ""
if (isUsingReact19(config) && packageManager === "npm") {
dependenciesSpinner.stopAndPersist()
logger.warn(
"\nIt looks like you are using React 19. \nSome packages may fail to install due to peer dependency issues (see https://ui.shadcn.com/react-19).\n"
)
const confirmation = await prompts([
{
type: "select",
name: "flag",
message: "How would you like to proceed?",
choices: [
{ title: "Use --force", value: "force" },
{ title: "Use --legacy-peer-deps", value: "legacy-peer-deps" },
],
},
])

if (confirmation) {
flag = confirmation.flag
}
}

dependenciesSpinner?.start()

await execa(
packageManager,
[packageManager === "npm" ? "install" : "add", ...dependencies],
[
packageManager === "npm" ? "install" : "add",
...(packageManager === "npm" && flag ? [`--${flag}`] : []),
...dependencies,
],
{
cwd: config.resolvedPaths.cwd,
}
)
dependenciesSpinner?.succeed()
}

function isUsingReact19(config: Config) {
const packageInfo = getPackageInfo(config.resolvedPaths.cwd)

if (!packageInfo?.dependencies?.react) {
return false
}

return /^(?:\^|~)?19(?:\.\d+)*(?:-.*)?$/.test(packageInfo.dependencies.react)
}
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ const CommandItem = React.forwardRef<
<CommandPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected='true']:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50",
"relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected='true']:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
className
)}
{...props}
Expand Down

0 comments on commit 64739f8

Please sign in to comment.