preact icon indicating copy to clipboard operation
preact copied to clipboard

Typing errors when using @headlessui/react with preact

Open kurtbuilds opened this issue 2 years ago • 2 comments

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

kurtbuilds avatar Jul 14 '23 02:07 kurtbuilds

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 for children becomes string and Element is not valid
  • When I add as="section" (or any valid DOM element) the expected type for className becomes (bag: TabsRenderPropArg) => string'
  • Without as, the expected type for className is any however it is defined as string | (bag: TabsRenderPropArg) => string'
  • Without as, Element children 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.

pm0u avatar Apr 03 '24 16:04 pm0u

Same issue on my side with Preact 10.20.1 and Transition component of headless ui 1.7.19

bagou4502 avatar Apr 05 '24 09:04 bagou4502