withastro / astro

The web framework for content-driven websites. ⭐️ Star to support our work!
https://astro.build
Other
46.26k stars 2.44k forks source link

Spread syntax into component that uses HTMLAttributes throws a TypeScript error #10780

Closed mrcsmcln closed 4 months ago

mrcsmcln commented 6 months ago

Astro Info

Astro                    v4.6.1
Node                     v18.18.0
System                   Linux (x64)
Package Manager          npm
Output                   static
Adapter                  none
Integrations             none

If this issue only occurs in one browser, which browser is a problem?

All browsers

Describe the Bug

Using the spread syntax on a component with a type that uses HTMLAttributes throws a TypeScript error.

For example, consider the following component, MyLink.astro:

---
import type { HTMLAttributes } from 'astro/types'

type Props = { text?: string } & HTMLAttributes<'a'>

const { text, ...props } = Astro.props
---

<a {...props}>
  <slot set:text={text} />
</a>

Next, we'll consume that component in another component, MyComponent.astro:

---
import type { ComponentProps } from 'astro/types'
import MyLink from './MyLink.astro'

type Props = {
  links: ComponentProps<typeof MyLink>[]
}

const { links } = Astro.props
---

<ul>
  {links.map((link) => <li><MyLink {...link} /></li>)}
</ul>

When you use <MyComponent> in, say, a page file and run astro check, TypeScript throws the following error:

Type '{ text?: string | undefined; 'class:list'?: string | Record<string, boolean> | Record<any, any> | Iterable<string> | Iterable<any> | undefined; ... 198 more ...; onfullscreenerror?: string | ... 1 more ... | undefined; }' is not assignable to type 'IntrinsicAttributes & { text?: string | undefined; } & HTMLAttributes<"a">'.
Type '{ text?: string | undefined; 'class:list'?: string | Record<string, boolean> | Record<any, any> | Iterable<string> | Iterable<any> | undefined; ... 198 more ...; onfullscreenerror?: string | ... 1 more ... | undefined; }' is not assignable to type 'IntrinsicAttributes'.

Removing either the spread syntax or swapping the HTMLAttributes<'a'> in favor of a more explicit type removes the error. My guess is because HTMLAttributes consumes a type that some information is lost and it's tripping up TS. If I could somehow do something like import type { HTMLAnchorAttributes } from 'astro/types' as seen here then that might solve the issue? That being said, I'm new to TypeScript so there could very well be something I'm missing.

If you open the minimal reproducible example link I've provided and run astro check, you'll see the same error.

What's the expected result?

I feel like this shouldn't throw an error. In my project, the data is all flowing through an object that generates components and the spread syntax is invaluable. I'm finding it impossible to create components the also accept a class or similar attributes.

Link to Minimal Reproducible Example

https://stackblitz.com/edit/github-fffdsv-s7i8nl?file=src%2Fpages%2Findex.astro&file=src%2Fcomponents%2FMyComponent.astro&file=src%2Fcomponents%2FMyLink.astro

Participation

florian-lefebvre commented 6 months ago

I can reproduce this issue (by downloading the stackblitz project locally). I'm not sure what's going on either, maybe @Princesseuh will know more

Callenowy commented 6 months ago

I have the same issue with an Input component in the below example.

---
// atoms/input.astro
import type { HTMLAttributes } from 'astro/types';

export interface Props
  extends Omit<HTMLAttributes<'input'>, 'id' | 'name' | 'class'> {
  id?: string;
  name: string;
}

const { name, id, ...inputProps } = Astro.props;
const componentId = id || name;
---

<input
  id={componentId}
  class="placeholder:text-cello-900/60 w-full rounded-lg border border-cello-300 bg-cello-50 p-4 text-base"
  {...inputProps}
/>
---
// moleculres/labeledInput.astro
import type { ComponentProps } from 'astro/types';

import Input from '../atoms/input.astro';

type InputProps = ComponentProps<typeof Input>;

type Props = InputProps & {
  label: string;
};

const { label, name, id, ...inputProps } = Astro.props;
const componentId = id || name;
---

<label
  for={componentId}
  class="block pb-1 text-xl font-normal leading-8 text-cello-900">{label}</label
>
<Input id={componentId} {...inputProps} />
Princesseuh commented 5 months ago

The issue here I'm pretty sure is that HTMLAttributes, kinda on purpose, does not include all Astro-specific attributes so it ends up being incompatible with IntrinsicAttributes. I wonder if we can just add the new properties, or if that'd break something else.

ArmandPhilippot commented 5 months ago

I have a similar issue with Typescript using astro/tsconfigs/strictest config:

[...] is not assignable to type 'IntrinsicAttributes' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties.

If I use astro/tsconfigs/strict instead the error message is the same, only:

[...] is not assignable to type 'IntrinsicAttributes'.

From my experience, the culprit is always slot. So to solve the issue in the reproduction we need to declare the Props with Omit:

type Props = {
  links: Omit<ComponentProps<typeof MyLink>, 'slot'>[]
}
Princesseuh commented 4 months ago

This is fixed as of the latest version of Astro. I think there's still some cases where a similar thing can happens, nonetheless this specific issue shouldn't happen anymore.

ferferga commented 2 months ago

Hello

This issue is still happening as of latest Astro, at least with the Image component. StackBlitz: https://stackblitz.com/edit/github-fffdsv-ar9vhd

All the relevant stuff is inside MyImage.astro and index.astro. Uncomment the specific line of each test and, between each, run npm run check to see the results.

If you prefer a new issue instead of reusing this one, don't hesitate in asking me to open a new one and apologize for the inconveniences. CC @Princesseuh @florian-lefebvre