Im trying to create a components lib by extending some of NextUI's components.
I wanted to start simple by doing the classic button. But unfortuantly I got stuck... when I build the npm package I get this:
stories/components/Button/Button.tsx(55,7): error TS2742: The inferred type of 'MyButton' cannot be named without a reference to '../../../node_modules/.pnpm/registry.npmjs.org+@[email protected]_@[email protected][email protected][email protected]/node_modules/@nextui-org/system-rsc/dist/types'. This is likely not portable. A type annotation is necessary.
stories/components/Button/Button.tsx(55,7): error TS2742: The inferred type of 'MyButton' cannot be named without a reference to '../../../node_modules/.pnpm/registry.npmjs.org+@[email protected][email protected]/node_modules/@nextui-org/react-utils/dist/refs'. This is likely not portable. A type annotation is necessary.
stories/components/Button/Button.tsx(55,7): error TS2742: The inferred type of 'MyButton' cannot be named without a reference to '.pnpm/registry.npmjs.org+@[email protected][email protected]/node_modules/@react-types/shared'. This is likely not portable. A type annotation is necessary.
stories/components/Button/Button.tsx(55,7): error TS2742: The inferred type of 'MyButton' cannot be named without a reference to '.pnpm/registry.npmjs.org+@[email protected][email protected]/node_modules/@react-types/shared'. This is likely not portable. A type annotation is necessary.
stories/components/Button/Button.tsx(55,7): error TS2742: The inferred type of 'MyButton' cannot be named without a reference to '.pnpm/registry.npmjs.org+@[email protected][email protected]/node_modules/@react-types/shared'. This is likely not portable. A type annotation is necessary.
stories/components/Button/Button.tsx(55,7): error TS2742: The inferred type of 'MyButton' cannot be named without a reference to '.pnpm/registry.npmjs.org+@[email protected][email protected]/node_modules/@react-types/shared'. This is likely not portable. A type annotation is necessary.
stories/components/Button/Button.tsx(55,7): error TS2742: The inferred type of 'MyButton' cannot be named without a reference to '.pnpm/registry.npmjs.org+@[email protected][email protected]/node_modules/@react-types/shared'. This is likely not portable. A type annotation is necessary.
stories/components/Button/Button.tsx(55,7): error TS2742: The inferred type of 'MyButton' cannot be named without a reference to '.pnpm/registry.npmjs.org+@[email protected][email protected]/node_modules/@react-types/shared'. This is likely not portable. A type annotation is necessary.
With this error the npm package loses it's type safety when used.
Maybe I'm doing something wrong. First I did an exakt copy of the this, (also gives the error).
Then I added my own variants to the button to test, the end result is this:
import { extendVariants, Button as NextUiButton } from '@nextui-org/react';
import { ReactNode, Ref } from 'react';
import { type VariantProps } from 'tailwind-variants';
const MyButton = extendVariants(NextUiButton, {
variants: {
// <- modify/add variants
color: {
primary: 'bg-blue-500 text-black',
secondary: 'bg-purple-500 text-black',
},
isDisabled: {
true: 'bg-[#eaeaea] text-[#000] opacity-50 cursor-not-allowed',
},
size: {
xs: 'px-unit-2 min-w-unit-12 h-unit-6 text-tiny gap-unit-1 rounded-small',
md: 'px-unit-4 min-w-unit-20 h-unit-10 text-small gap-unit-2 rounded-small',
xl: 'px-unit-8 min-w-unit-28 h-unit-14 text-large gap-unit-4 rounded-medium',
},
},
defaultVariants: {
// <- modify/add default variants
color: 'primary',
size: 'xl',
},
compoundVariants: [
// <- modify/add compound variants
{
isDisabled: true,
color: 'secondary',
class: 'bg-[#84cc16]/80 opacity-100',
},
],
});
interface MyButtonProps extends VariantProps<typeof MyButton> {
ref?: Ref<HTMLButtonElement>;
className?: string;
children?: ReactNode;
label: string;
}
export const Button = ({ size, className, color, label, children, ref }: MyButtonProps) => {
return (
<MyButton
{...{
ref,
className,
size,
color,
type: 'button',
}}>
{children}
{label}
</MyButton>
);
};
But the extraction of the types in the build step fires the above error.
Vite config:
import {defineConfig} from 'vite'
import react from '@vitejs/plugin-react'
import dts from 'vite-plugin-dts'
import {libInjectCss} from 'vite-plugin-lib-inject-css'
import {extname,relative,resolve} from 'path'
import {fileURLToPath} from 'node:url'
import {glob} from 'glob'
export default defineConfig({
plugins: [react(),libInjectCss(),dts({include: ['stories/components']})],
build: {
copyPublicDir: false,
lib: {
entry: resolve(__dirname,'stories/main.ts'),
formats: ['es'],
name: "my-components",
},
rollupOptions: {
external: ['react','react/jsx-runtime',"tailwind-variants","framer-motion",RegExp("^(@nextui-org/).+")],
input: Object.fromEntries(
glob.sync('stories/**/*.{ts,tsx,js,jsx}',{ignore: 'stories/**/*.stories.{ts,tsx,js,jsx}'}).map(file => [
relative(
'stories',
file.slice(0,file.length-extname(file).length)
),
fileURLToPath(new URL(file,import.meta.url))
])
),
output: {
assetFileNames: 'assets/[name][extname]',
entryFileNames: '[name].js',
}
},
},
})
And as a side note, if I create my own button using tailwind-variants everything works fine and I get type saftey in the component and no errors.
import { ReactNode, Ref } from 'react';
import { tv, type VariantProps } from 'tailwind-variants';
const button = tv({
base: 'font-medium bg-blue-500 text-white rounded-full active:opacity-80',
variants: {
color: {
primary: 'bg-blue-500 text-white',
secondary: 'bg-purple-500 text-white',
},
size: {
sm: 'text-sm',
md: 'text-base',
lg: 'px-4 py-3 text-lg',
},
},
compoundVariants: [
{
size: ['sm', 'md'],
class: 'px-3 py-1',
},
],
defaultVariants: {
size: 'md',
color: 'primary',
},
});
interface ButtonProps extends VariantProps<typeof button> {
ref?: Ref<HTMLButtonElement>;
className?: string;
children?: ReactNode;
label: string;
}
export const Button = ({ size, className, color, label, children, ref }: ButtonProps) => {
return (
<button
{...{
ref,
type: 'button',
title: label,
className: button({ className, color, size }),
}}>
{children}
{label}
</button>
);
};
EditTwo:
I have created a sandbox to show the issue.
Run pnpm build-sb
to create the component lib and get the error. And pnpm sb
just to start it, which work.
Check first if this is linked to
nextui-org/nextui
issue 1328, which mentioned:If not, you will need at least to address the type inference issues that TypeScript is encountering. TypeScript is struggling to infer the types for your extended component. By explicitly defining the types, you would provide clear information to TypeScript about what to expect:
Since the errors persist even after trying the solutions like
pnpm install --hoist
and switching to npm, the issue might be deeply rooted in the way TypeScript is handling type inference for your extended component.Since the issue is with the TypeScript's ability to infer types from the NextUI components, directly importing the types from NextUI's internal modules, if they are exported, might help. That is a bit of a workaround and depends on the internal structure of the NextUI package (... which might change in future updates, potentially breaking your implementation).
Another alternative approach would be to bypass type inference with
any
While this is not ideal in terms of maintaining type safety, as a last resort, you can bypass TypeScript's type checking for this specific case using
any
. This is for testing, as it negates the benefits of TypeScript's type system.Given the continued challenges with accessing the internal types of NextUI and the limitations of using
any
, you might need to explore some alternative approaches:One way to handle this is to declare a custom type that matches the expected structure of your extended component. That approach involves creating an interface that mirrors the props of the NextUI
Button
and then adding your custom props.Another approach is to decompose the NextUI
Button
component and reconstruct it with your additional functionality. That method can be more complex, but allows for greater control over the component's behavior and type handling.The OP references
microsoft/TypeScript
issue 42873.That issue does describe a similar problem where TypeScript fails to correctly infer types across different packages: when a package
B
depends on packageA
, andB
exports a value with a type defined or referenced byA
, TypeScript may produce an error similar to the one you are encountering.That issue matches your situation, where you are extending a component from NextUI and encountering type inference problems.
One suggestion from the issue discussion includes a workaround where setting
"declaration": false
and"declarationMap": false
intsconfig.json
resolved the problem for a user. However, this solution might not be ideal as it disables the generation of declaration files, which are essential for TypeScript's type checking in other projects that use your library.While waiting for a resolution, you might consider simplifying your component extension and limit the use of complex type inferences or generics. Also, consider using alternative libraries or design patterns that do not require extending components from third-party libraries in such a complex manner.
Note: this issue comment references:
And:
Extending NextUI components to leverage their built-in accessibility (a11y) features and animations is indeed a good idea. Since it is tricky with TypeScript (due to type inference issues), you might instead use them within your own custom components (composition over inheritance). That way, you can utilize NextUI's features (like a11y -- accessibility -- and animations), while adding your custom styles or logic through composition.
You can also try and create HOCs (Higher-Order Components) that wrap NextUI components, adding or modifying functionality as needed. That approach allows you to reuse NextUI's features while injecting additional props or styles.
If the customization involves logic more than styles, consider using custom hooks to encapsulate and reuse this logic across components.