palantir / blueprint

A React-based UI toolkit for the web
https://blueprintjs.com/
Apache License 2.0
20.64k stars 2.17k forks source link

Class components are incompatible with NextJS v13 (App Router) #6263

Open eco-front-end-team opened 1 year ago

eco-front-end-team commented 1 year ago

Environment

Code Sandbox

import { Button } from '@blueprintjs/core';

export default function Index() {
  return (
    <div className="">
      <h1>Home page</h1>
      <Button text='Refresh' intent='success' />
    </div>
  );
}

Steps to reproduce

Just run the NextJS app. It will throw following errors. I've tried different components still getting the same error.

Actual behavior

- error node_modules\tslib\tslib.es6.mjs (26:12) @ Module.__extends
- error Class extends value undefined is not a constructor or null

This might be caused by a React Class Component being rendered in a Server Component, React Class Components only works in Client Components. Read more: https://nextjs.org/docs/mes
sages/class-component-in-server-component
    at Module.__extends (webpack-internal:///(sc_server)/./node_modules/tslib/tslib.es6.mjs:58:54)
    at eval (webpack-internal:///(sc_server)/./node_modules/@blueprintjs/core/lib/cjs/common/abstractComponent.js:28:13)
    at eval (webpack-internal:///(sc_server)/./node_modules/@blueprintjs/core/lib/cjs/common/abstractComponent.js:106:2)
    at Object.(sc_server)/./node_modules/@blueprintjs/core/lib/cjs/common/abstractComponent.js (D:\projects\eco-pwa-next\.next\server\app\page.js:1668:1)
    at __webpack_require__ (D:\projects\eco-pwa-next\.next\server\webpack-runtime.js:33:42)
    at eval (webpack-internal:///(sc_server)/./node_modules/@blueprintjs/core/lib/cjs/common/index.js:28:27)
    at Object.(sc_server)/./node_modules/@blueprintjs/core/lib/cjs/common/index.js (D:\projects\eco-pwa-next\.next\server\app\page.js:1745:1)
    at __webpack_require__ (D:\projects\eco-pwa-next\.next\server\webpack-runtime.js:33:42)
    at eval (webpack-internal:///(sc_server)/./node_modules/@blueprintjs/core/lib/cjs/index.js:28:22)
    at Object.(sc_server)/./node_modules/@blueprintjs/core/lib/cjs/index.js (D:\projects\eco-pwa-next\.next\server\app\page.js:2944:1) {
  name: 'TypeError',
  digest: undefined
}
adidahiya commented 1 year ago

<Button> is not a class component in Blueprint v5.x.

Can you help me debug this? Is there a way you can repro this error with a code sandbox or stackblitz environment?

eco-front-end-team commented 1 year ago

<Button> is not a class component in Blueprint v5.x.

Can you help me debug this? Is there a way you can repro this error with a code sandbox or stackblitz environment?

https://stackblitz.com/edit/next-typescript-c1wmce?file=package.json

rdonadono commented 1 year ago

Same problem

This is my package.json I'm using node v16.20.1

"dependencies": {
    "@blueprintjs/core": "^5.0.1",
    "autoprefixer": "10.4.14",
    "eslint": "8.44.0",
    "eslint-config-next": "13.4.9",
    "next": "13.4.9",
    "postcss": "8.4.25",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "tailwindcss": "3.3.2",
    "typescript": "5.1.6"
},
switz commented 1 year ago

Also seeing this on the Icon component, I think it's due to file barrelling and not actually a component that's being rendered, but could be wrong.

edit: for some reason my blueprint is importing the cjs bundle, when it should be esm

if I do:

import { Icon } from '@blueprintjs/core/lib/esnext/index';

now the page renders, but getting errors from other components that are being imported:

- warn ../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/common/abstractComponent.js
Attempted import error: 'Component' is not exported from 'react' (imported as 'React').

Import trace for requested module:
../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/common/abstractComponent.js
../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/deprecatedTypeAliases.js
../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/index.js
./src/components/ui/button.tsx
./src/app/(extra)/(bare)/components/button/page.tsx

../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/common/abstractPureComponent.js
Attempted import error: 'PureComponent' is not exported from 'react' (imported as 'React').

Import trace for requested module:
../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/common/abstractPureComponent.js
../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/deprecatedTypeAliases.js
../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/index.js
./src/components/ui/button.tsx
./src/app/(extra)/(bare)/components/button/page.tsx

../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/components/button/buttons.js
Attempted import error: 'useState' is not exported from 'react' (imported as 'React').

Import trace for requested module:
../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/components/button/buttons.js
../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/components/index.js
../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/index.js
./src/components/ui/button.tsx
./src/app/(extra)/(bare)/components/button/page.tsx

../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/components/button/buttons.js
Attempted import error: 'useState' is not exported from 'react' (imported as 'React').

Import trace for requested module:
../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/components/button/buttons.js
../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/components/index.js
../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/index.js
./src/components/ui/button.tsx
./src/app/(extra)/(bare)/components/button/page.tsx

../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/components/button/buttons.js
Attempted import error: 'useRef' is not exported from 'react' (imported as 'React').

Import trace for requested module:
../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/components/button/buttons.js
../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/components/index.js
../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/index.js
./src/components/ui/button.tsx
./src/app/(extra)/(bare)/components/button/page.tsx

../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/components/context-menu/contextMenu.js
Attempted import error: 'useState' is not exported from 'react' (imported as 'React').

Import trace for requested module:
../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/components/context-menu/contextMenu.js
../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/components/index.js
../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/index.js
./src/components/ui/button.tsx
./src/app/(extra)/(bare)/components/button/page.tsx

../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/components/context-menu/contextMenu.js
Attempted import error: 'useState' is not exported from 'react' (imported as 'React').

Import trace for requested module:
../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/components/context-menu/contextMenu.js
../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/components/index.js
../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/index.js
./src/components/ui/button.tsx
./src/app/(extra)/(bare)/components/button/page.tsx

../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/components/context-menu/contextMenu.js
Attempted import error: 'useState' is not exported from 'react' (imported as 'React').

Import trace for requested module:
../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/components/context-menu/contextMenu.js
../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/components/index.js
../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/index.js
./src/components/ui/button.tsx
./src/app/(extra)/(bare)/components/button/page.tsx

../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/components/context-menu/contextMenu.js
Attempted import error: 'useRef' is not exported from 'react' (imported as 'React').

Import trace for requested module:
../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/components/context-menu/contextMenu.js
../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/components/index.js
../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/index.js
./src/components/ui/button.tsx
./src/app/(extra)/(bare)/components/button/page.tsx

../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/components/context-menu/contextMenu.js
Attempted import error: 'useEffect' is not exported from 'react' (imported as 'React').

Import trace for requested module:
../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/components/context-menu/contextMenu.js
../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/components/index.js
../../node_modules/.pnpm/@blueprintjs+core@5.0.1_@types+react@18.2.14_react-dom@18.2.0_react@18.2.0/node_modules/@blueprintjs/core/lib/esnext/index.js
./src/components/ui/button.tsx
./src/app/(extra)/(bare)/components/button/page.tsx
adidahiya commented 1 year ago

This issue sounds a bit like https://github.com/vercel/next.js/issues/39375 (although Blueprint is not specifying an "exports" field in package.json).

I tried changing to "moduleResolution": "nodenext" in tsconfig.json in your stackblitz but the same issue still shows up: https://stackblitz.com/edit/next-typescript-mvuuvj?file=tsconfig.json. This might be a bug in Next.js?

shinhwap commented 1 year ago

According to NextJS doc https://nextjs.org/docs/messages/class-component-in-server-component We need to render blueprintjs component only in client component I modified the stackblitz to client component It seems work, but I'm not sure if it is proper or not. https://stackblitz.com/edit/next-typescript-fxxvuk?file=src%2Fapp%2Fpage.tsx

tevonsb commented 1 year ago

We're using Blueprintjs extensively with in a nextjs project.

The key is the use client directive on top. I think the primary issue under this is that many bp components are class components, these don't work in SSR with nextjs.

@adidahiya any thinking about a refactor to function components? I saw that some of the bp components already are, are there any specific features of class components needed for the other ones? If not we'd be happy to help update them to react.FC, and maybe in meantime I'm happy to put something in the docs or as a discussion to label which components are classes and which are FC (and test that those work for SSR).

I haven't tested SSR with the FC components (like button), I'll give that a try tomorrow.

tevonsb commented 1 year ago

After looking through the code a bit, it looks like newer components are all FCs, and that the things being used on class components are:

  1. Animations
  2. Default props
  3. Prop validation

Default props would be easy to transition over, while I'm not sure how prop validation or animation would work (but can't yet find a component using the animation methods).

There are definitely some easy candidates for moving over to FCs (like slider), I'm happy to work on converting some of the low-hanging fruit if thats useful.

gluxon commented 1 year ago

@adidahiya any thinking about a refactor to function components?

Hey @tevonsb, I wanted to give a heads up that Adi is taking a well-deserved rest and will be back in September. I appreciate your offer to help. I suspect he'll have better informed thoughts than I will when he's back.

tevonsb commented 1 year ago

@gluxon great! No rush from our end, thanks for the heads up :)

adidahiya commented 1 year ago

@tevonsb what are the drawbacks from a UX perspective of using class components in Next.js applications? How does migrating every component to be an FC concretely help end-users? I'm willing to be convinced, but I would like to really understand the motivation here.

As you mentioned, newer Blueprint components are FCs. I also managed to migrate many simple components from classes to FCs in the latest major version, v5.0. I have an issue tracking the migration of other simple components for v6.0: https://github.com/palantir/blueprint/issues/6289. I'd be open to PRs for contributions there (not yet though; a few months from now when I create a next branch for v6.0 work with breaking changes).

Finally, the remaining class components are fairly complex, so migrating them accurately would require a decent amount of work. For example, DateRangeInput is 1000+ lines and has a lot of complex state management. Taking on this migration would need some compelling justification.

tevonsb commented 11 months ago

Hey @adidahiya Sorry for the delay on my end!

This issue is really helpful, having these simple components as function components I think is half the battle at least.

The compelling piece from a DX perspective is allowing for SSR. class components currently can only render on client, not using SSR (at least in nextjs world), according to this link, it seems that react server components require function components (given their dependence on async/await, correct me if I'm wrong on this!

So refactoring to function components would unlock the value of server components as well as the various improvements that react is introducing around them.

Given that nextjs and SSR are really taking off it seems quite beneficial for devs to be able to use them.

Like I mentioned we'd be happy to submit PRs helping convert the simple components! As well as digging into the more complex ones as well.

One thought could be to break the complex components out of core (like table etc already is), so that core is "complete" and devs could install additional components themselves and use the "use client" directive.

We're planning to do something like this already, pushing these complex components down to the leaves so that the majority of our app can render on server, then add client components at the end.