TypeScript makes some normally complex tasks very easy, but it can make some seemingly-simple tasks more difficult,
Change an API? Change the type and you'll see everywhere you need to update. Need to rename something used in 500 places? Hit F2 and rename.
TypeScript actively discourages highly-dynamic code, redefining variables at will, and APIs that put a lot of meaning into strings. What do these look like?
const sizes = {
"size-sm": 1,
"size-md": 2,
"size-lg": 3,
};
const getSize = (size: "sm" | "md" | "lg") => {
return sizes[`size-${size}`];
};
This site is all about letting you know about these limitations without all the type-system jargon you'll normally see.
TypeScript has a code for every error, such as ts(2741)
. While the error message may look very different each time, the underlying problem is the same.
If you're hitting a problem, search for that code within this page.
I'm trying to start with an empty object and add properties one by one, and TypeScript gives me errors no matter what I do. I've given up and // @ts-ignore
d them.
When you create an empty object, there's just no good way for TS to ensure that all properties have been added after the object has already been specified. Don't worry, though - there are lots of ways to accomplish this, and one of them is very concise as well! See: Build Up Object.
Diagnostic codes: ts(2339)
, ts(2741)
This is covered in the new handbook under the never type.
Additionally, arrays start out as never[]
when the type cannot be inferred. See: Never
TypeScript has a system called "excess property checking" which is a great tradeoff between type safety and developer experience... provided you know how it works. Luckily, it's pretty simple. See: Excess Property Checking.
Same question as above! Understanding Excess Property Checking will let you intentionally bypass this check where needed.
A nullable type is a union type, such as string | null
. To narrow types in a function like filter
, an extra annotation is needed for now (TypeScript might make an exception here in the future). See: Filter and Reduce.
You can! However, creating an empty object and building it up one property at a time isn't something that can really ever be checked for safety. A simple type assertion is typically all you need. A few different approaches are available at Filter and Reduce.
I have an array of mixed object types. Why can't I narrow based on whether or not a property exists?
You can! You just have to use an in
check to opt in to less-strict checking of properties. See the tradeoffs and other solutions in Type Guards.
This is usually an example of TS being especially careful with callbacks, as they may or may not be called synchronously. See: Type Widening.
Maybe! If all the options really are independently optional, that's fine. However, you may have a couple properties which are exclusive of one another. Think of a function that must accept either "a" or "b", but cannot accept both at once. If this covers your case, Dependent Properties can help you.
This can be one of a few issues.
If the library you're using involves two functions that quietly pass information between each other between imports (example: redux createStore()
and react-redux connect()
or useSelector()
), you might be interested in the Extending Modules section. This can let you define types once instead of on every usage.
If this isn't the case, your library is likely set up to accept generics. These are like function arguments, but for types. The syntax for these involves angle brackets (<>
) and can be tricky, so see Generics for details.
Finally, some libraries just don't have very good type definitions available, or are so dynamic that creating good library definitions isn't possible, or require types which TypeScript just can't support yet. This is rare, but an example is the humps library which converts all keys of an object from snake_case to camelCase. For these instances, you might want to use a Type Assertion. This ties into knowing when to bail out of types.
My function accepts string | number as a type and returns string | number. I know I gave it a string but now I have to check for numbers every time.
You're looking for Generics. These allow you to change the return value based on the arguments. Think of them like function arguments, but for types.
Right! Generics accept anything by default, so your add()
function suddenly needs to accept arrays, objects, and null
. You might want to restrict which types you accept using Bounded Type Parameters.
I'm using Object.keys() or Object.entries() and there's some index signature error. Why is the key ending up as a string
?
This one trips up everyone at some point. There's an explanation at Object.keys and Object.entries. Don't worry, there's a reason for it - and a few ways to opt-in to the behavior you want.
You're looking for the jargony ambient context
and ambient declaration
. When you put declare
in front of a function, constant, or module, you are saying "trust me, this exists, and is of this type." This is especially confusing when looking at the differences between .d.ts and .ts files, so see Types without implementations.
For most React errors, there's an excellect cheat sheet for using React with TypeScript. This is updated constantly with new information.
A React child is union of many, many possible things which cannot be narrowed except by complex type guards at every object depth. When iterating over children, it's probably best to use any
and distrust the existence of any property:
const containsDiv = React.Children.toArray(children).some((child: any) => {
return child?.type?.displayName === "Header";
});
You're returning something from a function component which React doesn't accept, likely undefined
. Your code might be fine now, but if it ever hits that undefined
case, you'll get a runtime error.
import React from "react";
interface Props {
text?: string;
}
const Button = ({ text }: Props) => {
return text && <button>{text}</button>;
};
If text
isn't supplied to Button, it'll return the current value of text
(which is undefined
). React currently throws in this case.
To get a better error message, add a return type annotation to your function component of either ReactElement
or ReactElement | null
. This will move the error to the return statement:
interface Props {
text?: string;
}
const Button = ({ text }: Props): ReactElement | null => {
return text ? <button>{text}</button> : null;
};
d.ts files are interesting. They allow you to declare the external API for your library without needing to specify
This can be challenging.
If you're writing library definitions, Testing Types can help you, by using the Conditional Type Checks library.