Typing errors when using @headlessui/react with preact
This repo contains a very basic react app, using a Disclosure component from @headlessui/react:
https://github.com/kurtbuilds/react-headlessui.git
Run tsc on that repo, and no errors are reported.
This identical project is using preact. The app functions as intended, but typescript (incorrectly) reports errors:
https://github.com/kurtbuilds/preact-headlessui-issue.git
The (first) error is Type 'string' is not assignable to type '(bag: DisclosureRenderPropArg) => string'. on the className property of the Disclosure jsx component.
Brief background on headlessui: the headlessui components are generic on the tag property (e.g. it renders in HTML as a <nav> element if you use the property as="nav"). Accordingly, the type is generic on that, and for some reason, I think those generics aren't working with preact right now. Following the full logic chain from headlessui/react:
Disclosure has the type defined here: https://github.com/tailwindlabs/headlessui/blob/076b03cf491629b1930531c4ca616902be6c1688/packages/%40headlessui-react/src/components/disclosure/disclosure.tsx#L430
interface ComponentDisclosure extends HasDisplayName {
<TTag extends ElementType = typeof DEFAULT_DISCLOSURE_TAG>(
props: DisclosureProps<TTag> & RefProp<typeof DisclosureFn>
): JSX.Element
}
and DisclosureProps ultimately gets the className property from ClassNameOverride in the Props type here: https://github.com/tailwindlabs/headlessui/blob/076b03cf491629b1930531c4ca616902be6c1688/packages/%40headlessui-react/src/types.ts#L48
If I manually test out those types, their logic is definitely working (also confirmed by it working in React), so I think the issue is something about the generic not working correctly.
I tried manually editing the ClassNameOverride type to see if there are any errors that crop up once this one is fixed. Unforunately, there's another error hidden behind this one.
TS2322: Type '{ children: ({ open }: { open: boolean; }) => Element; as: string; className: string; }' is not assignable to type 'IntrinsicAttributes & CleanProps<string, never> & OurProps<string, DisclosureRenderPropArg> & { ...; } & { ...; } & { ...; }'. Type '{ children: ({ open }: { open: boolean; }) => Element; as: string; className: string; }' is not assignable to type 'CleanProps<string, never>'. Property 'children' is incompatible with index signature. Type '({ open }: { open: boolean; }) => JSXInternal.Element' is not assignable to type 'never'.
Fortunately, I think this is a similar issue to the previous one. The stated error is about the children of the component (the child being a function ({open}) => JSXInternal.Element) is not assignable to never, which appears to come from a generic parameter from CleanProps. And I think that generic should be taking a value, rather than be never-valued.
To confirm that latter point, if you make a typo in the function in the react version of the project, you'll get an error: TS2339: Property 'ope' does not exist on type '{ open: boolean; }'., which confirms that the expected value for children of the Disclosure component, in the react version, does get correctly typed as ({open} => JSXInternal.Element.
I've managed to workaround this issue for now by remapping the types of the components as any:
import {
Disclosure as HDisclosure,
} from '@headlessui/react'
export const Disclosure: any = HDisclosure;
but obviously that eliminates any benefit from using typescript. If there are better workarounds until this gets solved, I'm curious to hear them.
I originally discussed this issue in https://github.com/preactjs/preact/discussions/4068
I am also running into this issue Preact 10.19.6 with the Tab component and associated components.
It seems to be something with the as prop on these components, which allows them to render as a dom element of your choosing.
- When I add
as="section"(or any valid DOM element) the expected type forchildrenbecomesstringandElementis not valid - When I add
as="section"(or any valid DOM element) the expected type forclassNamebecomes(bag: TabsRenderPropArg) => string' - Without
as, the expected type forclassNameisanyhowever it is defined asstring | (bag: TabsRenderPropArg) => string' - Without
as,Elementchildren do not throw a type error
There seems to be some issue resolving the conditional types generated by headlessui
I did find that react was being installed until i used the --legacy-peer-deps flag with npm i but the issue persists.
Same issue on my side with Preact 10.20.1 and Transition component of headless ui 1.7.19