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

[ESM Support][5.7.2] Unable to use react-select in ESM project #5595

Closed
dziraf opened this issue Mar 28, 2023 · 7 comments · Fixed by #5626
Closed

[ESM Support][5.7.2] Unable to use react-select in ESM project #5595

dziraf opened this issue Mar 28, 2023 · 7 comments · Fixed by #5626
Labels
issue/bug-unconfirmed Issues that describe a bug that hasn't been confirmed by a maintainer yet

Comments

@dziraf
Copy link

dziraf commented Mar 28, 2023

There are a couple of issues we currently have with using react-select in our projects that use "type": "module" in package.json and have "module" and "moduleResolution" set to "nodenext" in tsconfig.json.

First of all, importing.

Rough example:

import ReactSelect, { Props } from 'react-select'

// ...

const SomeComponent = (props: Props) => {
  return <ReactSelect {...props} />
}

This now fails when building types with: JSX element type 'ReactSelect' does not have any construct or call signatures.ts(2604)

We can resolve this ourselves by changing the above example to:

import ReactSelect, { Props } from 'react-select'

const Select = ReactSelect.default || ReactSelect
// ...

const SomeComponent = (props: Props) => {
  return <Select {...props} />
}

but it is not ideal.

Doing the above change allows us to build types without issue.

There is another issue which happens when we start the app though, which fails with:

(node:15999) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
/Users/rafaldziegielewski/Documents/projekty/adminjs-design-system/node_modules/react-select/dist/react-select.esm.js:1
import { u as useStateManager } from './useStateManager-7e1e8489.esm.js';
^^^^^^

SyntaxError: Cannot use import statement outside a module
    at internalCompileFunction (node:internal/vm:74:18)
    at wrapSafe (node:internal/modules/cjs/loader:1141:20)
    at Module._compile (node:internal/modules/cjs/loader:1182:27)
    at Module._extensions..js (node:internal/modules/cjs/loader:1272:10)
    at Module.load (node:internal/modules/cjs/loader:1081:32)
    at Module._load (node:internal/modules/cjs/loader:922:12)
    at ModuleWrap.<anonymous> (node:internal/modules/esm/translators:169:29)
    at ModuleJob.run (node:internal/modules/esm/module_job:194:25)

Node.js v18.13.0
error Command failed with exit code 1.

The above error can be resolved by adding "type": "module" to every package.json of react-select, for example here:
https://github.com/JedWatson/react-select/blob/master/packages/react-select/base/package.json
and adding .js extensions to exports.

We managed to resolve this issue in our local environment by creating a patch:
https://gist.github.com/dziraf/160b203a8837b85a76a1f42e7b22d524
The issue still remains though as we're using it inside of one of our libraries and patches are unfortunately not installed recursively inside of node_modules when you install them.

@dziraf dziraf added the issue/bug-unconfirmed Issues that describe a bug that hasn't been confirmed by a maintainer yet label Mar 28, 2023
@dziraf
Copy link
Author

dziraf commented Mar 28, 2023

The typing issue basically boils down to: microsoft/TypeScript#50482
Specifically this comment (microsoft/TypeScript#50482 (comment)):

Their package.json says "type": "module" but their .d.ts files use extensionless imports. Analyzing exactly what they should do instead is frankly a lot of work, but I can confirm it’s expected behavior that it’s not working for you under nodenext. At a glance, it looks like things would probably Just Work™ if their .d.ts files all used .js extensions in their imports, but the real happy path for dual CJS/ESM packages is to have dual CJS/ESM type declarations that can accurately represent the nuances between the two modes rather than trying to massage a single set of definitions into a format that mostly works for both modes—but that is sometimes possible, particularly when default exports aren’t used.

@fortezhuo
Copy link

fortezhuo commented Mar 29, 2023

Hi, I have this problem too today whiler refresh the page. After investigate the issue, I had solved this problem by dynamic import react-select (lazy loading) using React.Suspense.

Hope this clue can solve and brighten your day

@dziraf
Copy link
Author

dziraf commented Mar 29, 2023

@fortezhuo Yes, I tried lazy loading yesterday and it also worked for me.

@joshball
Copy link

@fortezhuo or @dziraf
Could you share your code for getting this to work?
I have tried:

const Select = React.lazy(() => import("react-select"));
...
// in component:
        <Suspense fallback={<div>Loading...</div>}>
          <Select options={options} />
        </Suspense>

And it stays stuck in Loading...

@fortezhuo
Copy link

fortezhuo commented Mar 31, 2023

@joshball

U can add script isMounted to make sure react select only mounted on client side

 const [isMounted, setMounted] = useState<boolean>(false);
 useEffect(() => {
   if (!isMounted) {
     setMounted(true);
   }
 })

 return <Suspense fallback={<div>Loading...</div>}>
      {isMounted && <Select options={options} />}
  </Suspense>

If you want something more modular, u can create function to lazy loading

export function dynamic(load: () => Promise<any>, option?: LazyOption) {
  const Component = lazy(load)
  const Lazy = function (props: any) {
    const [isMounted, setMounted] = useState<boolean>(false);

    useEffect(() => {
      if (!isMounted) {
        setMounted(true);
      }
    })

    return <Suspense fallback={option?.fallback || <div className="loader">Loading</div>}>
      {isMounted && <Component {...props} />}
    </Suspense>
  }

  return Lazy
}

const ReactSelect = dynamic(async () => await import("react-select"))

@cpmsmith
Copy link

I'm seeing this too, manifesting as a lint error being thrown by eslint-plugin-import's extensions rule:

Missing file extension for "react-select/async"  import/extensions

This error is thrown on 5.7.1, but not 5.7.0. Something about this setup is preventing eslint-import-resolver-webpack from being able to resolve the react-select package at all:

$ npm install react-select@5.7.0

changed 1 package, and audited 305 packages in 403ms

82 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
$ node -e 'console.log(require("eslint-import-resolver-webpack").resolve("react-select", process.env.PWD + "/index.mjs"))'
{
  found: true,
  path: '[...]/node_modules/react-select/dist/react-select.esm.js'
}
$ npm install react-select@5.7.1

changed 1 package, and audited 305 packages in 405ms

82 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
$ node -e 'console.log(require("eslint-import-resolver-webpack").resolve("react-select", process.env.PWD + "/index.mjs"))'
{ found: false }

@marcelgerber
Copy link

I'm seeing this too.
I think the most robust change here would be to rename the build assets to .mjs and .cjs, and list these inside the package.json exports field.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
issue/bug-unconfirmed Issues that describe a bug that hasn't been confirmed by a maintainer yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants