Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add value type generic to ComboboxOptionsProp #3394

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

kradical
Copy link
Contributor

@kradical kradical commented Jul 19, 2024

Hi I'm back 👋, another small typescript tweak, no functionality change.

Summary

Adding a generic here lets consuming code specify a type for an option. This is useful when operating in virtual mode and passing a callback as a child. To get it to be well typed consumers would do something like:

type Person = { name: string };

<ComboboxOptions<'div', Person>>
  {({ option }) => <div>{option.name}</div>}
</ComboboxOptions>

Getting nice strong typing on option. Slightly nicer to specify a generic type then force a cast in the callback imo. Also in situations where somebody is combining multiple combobox components in some generic component they can very logically apply TValue here and in other places too.

Notes

I defaulted this new generic type to unknown which is definitely a type "breaking" change. someone upgrading will possibly experience a compiler error. As the old type for option was any. imo unknown is definitely the correct choice here to force the consumer to make a decision on what their thing is.. but I can also appreciate not wanting to break backwards compatibility. So feel free to update it to default to any (in both places) or I can, if that is what you want.

We have this in our codebase right now:

/**
 * Slightly better version of headless ui's internal OptionsRenderPropArg type. This way we can
 * provide a generic type parameter
 */
interface ComboboxOptionRenderProps<T> {
  open: boolean;
  option: T;
}
type ComboboxOptionsProps<TValue = unknown> = Omit<
  HeadlessComboboxOptionsProps,
  "children"
> & {
  children?:
    | ReactNode
    | ((bag: ComboboxOptionRenderProps<TValue>) => ReactElement);
};

And it would be pretty nice to support this in headlessui-react! thank you!

Copy link

vercel bot commented Jul 19, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
headlessui-react ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jul 25, 2024 4:39pm
headlessui-vue ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jul 25, 2024 4:39pm

@RobinMalfait
Copy link
Member

Hey!

Thanks for the PR, I think we should use any instead of unknown because we recently changed it such that users can override the option in the render prop (see: #3327)

The additional type is definitely a breaking change because the default value will only work if you don't pass anything. So if you currently have ComboboxOptionsProps<'div'> then you will get a type error. (Or maybe that's only in older versions of TypeScript now thta I think about it)

What do you think about this (breaking) change, @thecrypticace?

I don't hate this change honestly, but the only thing that might not be as ideal is that the type exists but it is only used when in virtual mode, which is why I like typing it inside of the render prop function directly. But I can be convinced of this change though!

@kradical
Copy link
Contributor Author

kradical commented Jul 25, 2024

Sounds good RE: any vs. unknown, thanks for the link. Edit: updated PR with any

The additional generic type should be fine for backwards compatibility, it'll pick up the default "any" in the situation where a consumer has ComboboxOptionsProps<'thing'>. See this ts playground: https://www.typescriptlang.org/play/?#code/C4TwDgpgBAKg7gewOIQHYQE4EsDGBnAHhigF4oByAEywDdyAaKAVVKgENUQA+VgbwCgoQ9gC5Y9QcIBGYphIC+-fjgSo8wKMAjqAjGPjI0mXHj6iK1OoxlQdAJgDMUeQG5lq9Zu3A7+xCnRsfAJyVARgS3IeMl5zUPDI6zF7J1d3NQ0tdQc-Q0CTELCI2gYoVABXAFspTABtAF1oqFi2MXjiqygbWpT65zcgA

interesting, I see what you're saying. I think long term unifying the usage pattern between virtual and non virtual could be good. Or maybe allow non-virtual combobox'es to pass options + template function instead of rendering option nodes. But I'm totally content with how it works now.

Adding a generic here lets consuming code specify a type for an option. This is
useful when operating in virtual mode and passing a callback as a child. To get
it to be well typed consumers would do something like:

```jsx
type Person = { name: string };

<ComboboxOptions<'div', Person>>
  {({ option }) => <div>{option.name}</div>}
</ComboboxOptions>
```

Getting nice strong typing on option. Slightly nicer to specify a generic type
then force a cast in the callback imo. Also in situations where somebody is
combining multiple combobox components in some generic component they can
very logically apply TValue here and in other places too.
@kradical
Copy link
Contributor Author

Hey @RobinMalfait I was wondering if you had a chance to review my updates? I swapped it to default to any so it should be completely backwards compatible (as I kind of showed in the typescript playground linked above).

@mktcode
Copy link

mktcode commented Aug 28, 2024

Already closed but I'm linking this issue, because it's what I found first and almost forgot to check the PRs.
#1332

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants