Reputation: 6825
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
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
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
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