-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Ensure onChange
types are contravariant instead of bivariant
#3781
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
Conversation
This will make the types contravariant instead of bivariant. See: https://www.typescriptlang.org/play/?#code/MYewdgzgLgBMCGAbRB1AllAFgWQKZZABMYBeGACgAcAnESiALhgG8ZwBhTeMAc13IBuSAK64mYYchgAfGNGppeASiYCQaYgF8lpAHwtNAKEOhIsBMnRYACrUq5qUAJ6kYFGnUYs2YTtz5MgiJiMBJSsvKKPDok+moaMNp6Bsam0HBIiABG8MAA1q5BiKJMkcrJQsW4qeDp8NQ8rqwcXLwhFtm5BUYmmVY4+JhE5PXRvZYYmLZ0Ds4jDUpAA
The latest updates on your projects. Learn more about Vercel for GitHub.
|
} | null | ||
|
||
onClose?(): void | ||
onClose?: () => void |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not necessary in this case, but more consistent
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wild to me that TypeScript works this way but I guess they had to preserve some behavior for backwards compatibility which is why methods in objects act differently than function properties
Note: I believe that his change broke compilation for me, despite being included in a patch version:
|
@baraknaveh this is a trade-off I was willing to make. This change didn't introduce a bug/issue but it exposed one. We've always called the If you look at an example like this. Then the type would be <Combobox onChange={ x => {
// ^? string | null
} } /> However, if you don't inline the function and pass in a function instead that doesn't adhere to this signature, then TypeScript was also fine with it: function handleChange(x: string) {
x.length // TypeScript is fine with it, but it would crash at runtime
}
<Combobox onChange={handleChange} /> We now ensure that the type of that - function handleChange(x: string) {
+ function handleChange(x: string | null) {
x.length // TypeScript is fine with it, but it would crash at runtime
} So while this change can trigger compile time errors now, I believe this is a much better spot to expose bugs than at runtime. This is unfortunately one of those cases where every bugfix is a breaking change if you rely on the bug's behavior. But hopefully with this explanation, it makes sense why it's part of a patch release (because the bug was always there at runtime). |
We're also getting an error after upgrading to v2.2.8. Here's the rough setup: We have a component that wraps a combobox: type Option = { id: string; name: string }
type Props = {
options: Option[]
label?: string
onChange?: (option: Option) => void
value?: Option
active?: boolean
}
export const DropdownWithSearch: FC<Props> = ({
options,
label,
value,
onChange,
active = true,
}) => {
// …
return (
<Combobox value={filteredOptions.find(({ id }) => selected.id === id)} onChange={onChange}>
All the other stuff is here
</Combobox>
)
} There's also a preview/test for this that almost directly follows what's shown in the docs: import { type FC, useState } from "react"
import { DropdownWithSearch } from "."
const options = [
{
id: "1",
name: "Option 1",
},
{
id: "3",
name: "Optione 2",
},
{
id: "2",
name: "Optionen 3",
},
]
export const DropdownWithSearchPreview: FC = () => {
const [selected, setSelected] = useState(options[0])
return (
<div className="flex items-end gap-x-8 bg-white px-6 py-2">
<DropdownWithSearch
value={selected}
onChange={setSelected}
options={options}
label="Label for"
/>
<DropdownWithSearch
value={selected}
onChange={setSelected}
options={options}
label="Label for inactive"
active={false}
/>
</div>
)
} Before the change, it worked; in v2.2.8, I'm getting a type error for
When I change the type like so: type Props = {
options: Option[]
label?: string
onChange?: (option: Option | null) => void // <= changed `Option` to `Option | null`
value?: Option
active?: boolean
} Then the type error in the component's
Did I read something wrong and the |
That is correct, the missing piece is this line: const [selected, setSelected] = useState(options[0]) This is now typed as The fact that it can be If you don't want it to be null, then you can technically do this: - onChange={setSelected}
+ onChange={value => {
+ if (value) {
+ setSelected(value)
+ }
+ }} This guarantees that the A use case where we call the |
Thanks for explaining, Robin! That indeed solved it, although I also had to change the type of type Props = {
options: Option[]
label?: string
onChange?: (option: Option | null) => void
value?: Option | null // <= this can't just be `Option` any longer
active?: boolean
} Out of curiosity: Where would I find such a thing in the docs? I can't seem to find docs regarding TypeScript… |
@RobinMalfait not challenging the usefulness of the change. I just flagged that it's compilation breaking, thus by semver convention, requires a major version bump (ie., it's not a patch version). |
@RobinMalfait What is the recommended way to create non nullable inputs now? In my case, I have a Listbox with controlled non-nullable state, but the listbox won't accept my |
@clemens we don't have TypeScript related docs on its own, but you should be able to get all the information from the components in your editor. We also talked about the @baraknaveh Since the bug was always there, we considered it a bug fix, because it actually fixes a (runtime) bug. Like I mentioned earlier, every bug fix can be a breaking change if you relied on the bug. @dd-jonas In Headless UI v2 we've always had nullable Comboboxes, this PR didn't change that. It just fixed the types to be more correct. Even with the code you had before this PR, the In your case, you can use: function handleChange (x: string | null) {
if (x === null) return // Just ignore `null`, or handle `null` however you want
// Proceed as before
} |
HeadlessUI changed the type declaration of Combobox onChange tailwindlabs/headlessui#3781
This PR fixes an issue where the types for certain event handlers such as
onChange
on theCombobox
component were incorrectly typed as bivariant instead of contravariant.This is now fixed by using function property syntax instead of method syntax.
Before:
After:
In both cases:
A pure TypeScript example, thanks to @mn-prp: https://www.typescriptlang.org/play/?#code/MYewdgzgLgBMCGAbRB1AllAFgWQKZZABMYBeGACgAcAnESiALhgG8ZwBhTeMAc13IBuSAK64mYYchgAfGNGppeASiYCQaYgF8lpAHwtNAKEOhIsBMnRYACrUq5qUAJ6kYFGnUYs2YTtz5MgiJiMBJSsvKKPDok+moaMNp6Bsam0HBIiABG8MAA1q5BiKJMkcrJQsW4qeDp8NQ8rqwcXLwhFtm5BUYmmVY4+JhE5PXRvZYYmLZ0Ds4jDUpAA
The
onChange
was the important one, but fixed the other spots where we use method syntax as well.Fixes: #3727