artooras
artooras

Reputation: 6825

NextJS dynamic import issue

I'm facing a really strange issue with dynamic import from NextJS.

I'm importing a component like so:

const Spinner = dynamic(() => import('components/ui/Spinner').then(mod => mod.Spinner))

And the Spinner.tsx is

import {useEffect, useState} from 'react'
import PulseLoader from 'react-spinners/PulseLoader'

import {FadeHOC} from '.'
import theme from 'utils/theme'


interface Props {
  inline?: boolean
}

const TIMEOUT = 1000

export const Spinner = ({inline}: Props) => {

  const [show, setShow] = useState(false)

  useEffect(() => {
    
    const timeout = setTimeout(
      () => setShow(true),
      TIMEOUT
    )
    
    return () => clearTimeout(timeout)
  }, [])

  return (
    show
      ? <FadeHOC>
          <PulseLoader size={10} margin={3} color={theme.color} css={inline ? 'margin-left: 14px;' : 'display: block; text-align: center; margin: 100px auto;'} />
        </FadeHOC>
      : null
  )
}

And on the dynamic import statement I get a TypeScript complaint:

Argument of type '() => Promise<(({ inline }: Props) => JSX.Element) | ComponentClass<never, any> | FunctionComponent<never> | { default: ComponentType<never>; }>' is not assignable to parameter of type 'DynamicOptions<{}> | Loader<{}>'.
  Type '() => Promise<(({ inline }: Props) => JSX.Element) | ComponentClass<never, any> | FunctionComponent<never> | { default: ComponentType<never>; }>' is not assignable to type '() => LoaderComponent<{}>'.
    Type 'Promise<(({ inline }: Props) => Element) | ComponentClass<never, any> | FunctionComponent<never> | { default: ComponentType<never>; }>' is not assignable to type 'LoaderComponent<{}>'.
      Type '(({ inline }: Props) => Element) | ComponentClass<never, any> | FunctionComponent<never> | { default: ComponentType<never>; }' is not assignable to type 'ComponentType<{}> | { default: ComponentType<{}>; }'.
        Type '({ inline }: Props) => JSX.Element' is not assignable to type 'ComponentType<{}> | { default: ComponentType<{}>; }'.
          Type '({ inline }: Props) => JSX.Element' is not assignable to type 'FunctionComponent<{}>'.
            Types of parameters '__0' and 'props' are incompatible.
              Type '{ children?: ReactNode; }' has no properties in common with type 'Props'.ts(2345)

I can't figure out what the complaint is about, and I'd really appreciate any help!

Upvotes: 14

Views: 19198

Answers (3)

ArifMustafa
ArifMustafa

Reputation: 4965

I was also facing same issue with NextJS v14 with Typescript, tried above said approach didn't resolved my issue, hence did following changes in tsconfig.json -

{ 
  "compilerOptions": {
    "lib": ["dom", "dom.iterable", "esnext", "ES2020"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "target": "ES2019",
    "esModuleInterop": true,
    "module": "ES2020",
    "moduleResolution": "Bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "plugins": [
      {
        "name": "next"
      }
    ],
    "paths": {
      "~/*": ["./*"]
    },
    "typeRoots": ["./types", "./node_modules/@types"]
  },
  ...
}

setting the module as ES2020 and moduleResolution as Bundler resolved my issue following typescript moduleResolution doc.

'bundler' for use with bundlers. Like node16 and nodenext, this mode supports package.json "imports" and "exports", but unlike the Node.js resolution modes, bundler never requires file extensions on relative paths in imports.

Hope this example, would also help manyone.

Upvotes: 0

Danila
Danila

Reputation: 18566

@brc-dd is right, just define the component with React.FC type,

export const Spinner: React.FC<Props> = ({inline}) => {}

although small explanation why it works like that:

dynamic is typed like that:

export default function dynamic<P = {}>(dynamicOptions: DynamicOptions<P> | Loader<P>, options?: DynamicOptions<P>): React.ComponentType<P>;

So it expects you to return React.ComponentType<P> and it means:

type ComponentType<P = {}> = ComponentClass<P> | FunctionComponent<P>;

and FunctionComponent is:

interface FunctionComponent<P = {}> {
        (props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
        propTypes?: WeakValidationMap<P>;
        contextTypes?: ValidationMap<any>;
        defaultProps?: Partial<P>;
        displayName?: string;
    }

So you have to have props with children key. If you don't want to mess up your component interface and it's actually not accepting children then you can just type children?: never; in component Props interface.

Upvotes: 0

Slava
Slava

Reputation: 2007

dynamic is typed like that:

export default function dynamic<P = {}>(dynamicOptions: DynamicOptions<P> | Loader<P>, options?: DynamicOptions<P>): React.ComponentType<P>;

where P is props of the imported component

So, dynamic expects to get Loader<P> = LoaderComponent<P> = Promise<React.ComponentType<P>

You just need to tell him what the props are. Like this:

const Spinner = dynamic<{ inline?: boolean }>(
  () => import('components/ui/Spinner').then(mod => mod.Spinner)
)

or

import { Spinner as StaticSpinner } from 'components/ui/Spinner'

const Spinner = dynamic<React.ComponentProps<typeof StaticSpinner>>(
  () => import('components/ui/Spinner').then(mod => mod.Spinner)
)

or make Props as exported and then:

import { Props as SpinnerProps } from 'components/ui/Spinner'

const Spinner = dynamic<SpinnerProps>(
  () => import('components/ui/Spinner').then(mod => mod.Spinner)
)

Upvotes: 19

Related Questions