Create autocomplete helper which allows for arbitrary values
Sometimes you run into an issue where you want some autocomplete, but not total autocomplete. For instance, here, we have a React component, which takes in a prop, which is a size
. And this size here
can be either sm
or xs
.
export const Icon = (props: IconProps) => {
return <></>
}
const Comp1 = () => {
return (
<>
<Icon size="xs"></Icon>
<Icon size="something"></Icon>
</>
)
}
But imagine that we design an API where you can either pass in sm
, xs
, or any arbitrary value. What you might think is the right thing to do is to add string
to the IconSize
union type.
type IconSize = "sm" | "xs" | string
This seems to make sense, except that you lose all the autocomplete on the size
prop, which isn't really what you want. The reason this happens is that TypeScript is quite clever with union types, and it tries to kind of pull in all of the types that it can.
For instance, if you specify the same type twice, like xs
, then it won't show up twice in the autocomplete.
type IconSize = "sm" | "xs" | "xs"
The reason this is happening with a string, is that TypeScript is reasoning since xs
and sm
are both strings, and you're also specifying string
, that it means it should just removed xs
and sm
and only offer string. Preventing your from getting autocomplete.
The way we can get around this is we can actually use the Omit
utility type, and we can omit xs
or sm
from the union. And now TypeScript won't collapse. these three things, and you'll have autocomplete again.
type IconSize = "sm" | "xs" | Omit<string, "xs", "sm">
We can even turn this into a type helper if we want to.
type LooseAutocomplete<T extends string> = T | Omit<string, T>
This is a way of just taking this information up in our IconSize
type and turning it into a type helper. And now we can just wrap sm
and xs
in this type helper.
type IconSize = LooseAutocomplete<"sm" | "xs">
And it's gonna work for us. So again, xs
, sm
, and any arbitrary value.
Transcript
0:00 Sometimes, you run into an issue where you want some autocomplete, but not total autocomplete. For instance, here, we have a React component which takes in a prop which is a size. This size here can be either SM or XS.
0:16 Imagine that we design an API where you can either pass in SM, XS, or can you pass in any arbitrary value. Here, what you might think is the right thing to do is go "or string" here. This is a union type, so it has SM or XS or string. This seems to make sense, except that you lose all the autocomplete on the size prop, which isn't really what you want.
0:41 The reason this happens is that TypeScript is quite clever with union types. It tries to pull in all of the types that it can. For instance, if you specify the same type twice, like XS, then it won't show up twice in the autocomplete. You'll just have SM and XS.
1:00 The reason that this is happening with the string is it's saying, "OK, SM is a string, and XS is also a string. What we need to do is remove these two, and then only offer string," which is why you don't get any autocomplete. The way we can get around this is we can actually use the omit utility type.
1:19 We can omit XS or SM from the union. Now, TypeScript won't collapse these three things, and you'll have the autocomplete here. You'll have SM, XS, and you'll be able to pass any other arbitrary value. We can even turn this into a type helper, if we want to.
1:37 We can say type loose autocomplete with a T, and we're going to say extend string, just because we're dealing with strings here, and we're going to say T, or we can have omit string T. This is a way of just taking this information up here and turning it into type helper.
2:00 Now, we can just wrap this with loose autocomplete, and it's going to work for us. Again, XS, SM, and any arbitrary value.
Ever wanted just a bit of autocomplete? Here, we create a TypeScript helper called LooseAutocomplete which gives us autocomplete while also allowing arbitrary values.
Picked up this tip from @GavinRayDev - worth a follow!
More Tips
Type Predicates
1 min
TypeScript 5.1 Beta is OUT!
2 mins
How to Name your Types
4 mins
Don't use return types, unless...
4 mins
TypeScript 5.0 Beta Deep Dive
6 mins
Conform a Derived Type Without Losing Its Literal Values
1 min
Avoid unexpected behavior of React’s useState
1 min
Understand assignability in TypeScript
2 mins
Compare function overloads and generics
1 min
Use infer in combination with string literals to manipulate keys of objects
1 min
Access deeper parts of objects and arrays
1 min
Ensure that all call sites must be given value
1 min
Understand how TypeScript infers literal types
1 min
Get a TypeScript package ready for release to NPM in under 2 minutes
1 min
Use assertion functions inside classes
1 min
Assign local variables to default generic slots to dry up your code and improve performance
2 mins
Know when to use generics
2 mins
Map over a union type
1 min
Make accessing objects safer by enabling 'noUncheckedIndexedAccess' in tsconfig
1 min
Use generics to dynamically specify the number, and type, of arguments to functions
1 min
Use 'declare global' to allow types to cross module boundaries
2 mins
Turn a module into a type
2 mins
Use deep partials to help with mocking an entity
1 min
Throw detailed error messages for type checks
1 min
Create a 'key remover' function which can process any generic object
1 min
Use generics in React to make dynamic and flexible components
1 min
Create your own 'objectKeys' function using generics and the 'keyof' operator
1 min
Write your own 'PropsFrom' helper to extract props from any React component
1 min
Use 'extends' keyword to narrow the value of a generic
1 min
Use function overloads and generics to type a compose function
2 mins
Decode URL search params at the type level with ts-toolbelt
2 mins
Use 'in' operator to transform a union to another union
2 mins
Derive a union type from an object
2 mins