Explained: 'React' refers to a UMD global
Find out why this error occurs and learn how to fix it.
As a frontend developer, your job isn't just pixel-pushing. Most of the complexity in frontend comes from handling all the various states your app can be in.
It might be loading data, waiting for a form to be filled in, or sending a telemetry event - or all three at the same time.
If you aren't handling your states properly, you're likely to come unstuck. And handling states starts with how they're represented in types.
Let's imagine you're building a simple data loader. You might choose to use a type like this to represent its state:
typescript
interface State {status: "loading" | "error" | "success";error?: Error;data?: { id: string };}// Some examples:const example: State = {status: "loading"};const example2: State = {status: "error",error: new Error("Oh no!")};
This seems pretty nice - we can check status
to understand what kind of UI we should display on the screen.
Except - this type lets us declare all sorts of shapes which should be impossible:
typescript
const example3: State = {status: "success",// Where's the data?!};
Here, we're in a success state - which should let us access our data. But it doesn't exist!
typescript
const example4: State = {status: "loading",// We're loading, but we still have an error?!error: new Error("Eek!"),};
And here, we're in a loading state - but there's still an error in our data object!
This is because we've chosen to represent our state using what I call a 'bag of optionals' - an object full of optional properties.
Optional properties are best used when a value might or might not be present. In this case, that isn't right.
status
is loading
, data
or error
are never present.status
is success
, data
is always present.status
is error
, error
is always present.The more accurate way to represent this is using a discriminated union.
Let's start by changing our state to be a union of object, each containing a status.
typescript
type State =| {status: "loading";}| {status: "success";}| {status: "error";};
Now that we've got our scaffolding, we can start adding elements to each branch of the union. Let's re-add our error and data types.
typescript
type State =| {status: "loading";}| {status: "success";data: {id: string;};}| {status: "error";error: Error;};
Now, our examples from above will start erroring.
typescript
// Error: Property 'data' is missingconst example3: State = {status: "success",};const example4: State = {status: "loading",// Error: Object literal may only specify known// properties, and 'error' does not existerror: new Error("Eek!"),};
Our State
type now properly represents all the possible states of the feature. That's a big step forward, but we're not done yet.
Let's imagine we're inside a component in our codebase. We've received our piece of state, and we're looking to use it to render some JSX.
I'll use React here, but this could be any frontend framework.
The first instinct of many developers will be to destructure the elements of State
, but you'll immediately hit errors:
typescript
const Component = () => {const [state, setState] = useState<State>({status: "loading",});const {status,// Error: Property 'data' does not exist on type 'State'.data,// Error: Property 'error' does not exist on type 'State'.error,} = state;};
For many devs, this is going to be tricky to figure out. Both data
and error
can exist on State
, so why am I getting errors?
The reason is that we haven't tried to discriminate the union yet! We don't know which state we're in, so the only properties available are the ones which all the members of the union share. Namely, status
.
Once we've checked which branch of the union we're in, we can safely destructure state
!
typescript
if (state.status === "success") {const { data } = state;}
This strictness is a feature, not a bug. By ensuring you can only access data when the status equals success
, you're encouraged to think of your app in terms of its states, and only access data in the states it's available.
When you start thinking of your app in terms of discriminated states, a lot of things get easier.
Instead of a big optional bag of data, you'll start understanding the connections between data and UI.
Not only that, but you'll be able to think about props in a whole new way.
What if you need to display a component in two slightly different ways? Use a discriminated union:
typescript
type ModalProps =| {variant: "with-description-and-button";buttonText: string;description: string;title: string;}| {variant: "base";title: string;};
Here, buttonText
and description
will only be required when the variant passed in is with-description-and-button
.
Beautiful.
Share this article with your friends
Find out why this error occurs and learn how to fix it.
Learn the differences between React.ReactNode
and JSX.Element
in TypeScript when working with React.
Since I first got into advanced TypeScript, I've been in love with a particular pattern. It formed the basis for one of my first-ever TypeScript tips, and it's been extraordinarily useful to me ever since. I call it the IIMT (rhymes with 'limped'): the Immediately Indexed Mapped Type.
There are three rules to keep in mind when deciding where to put types in your application code.
Discover the power of ComponentProps in React and TypeScript. Learn how to extract and utilize props from native HTML elements, existing components, and elements with associated refs. Enhance your development workflow with this essential type helper.
Testing types is a crucial aspect of developing libraries in TypeScript. In this article, we explore three different ways to test your types: using the vitest test runner, rolling your own solution with type helpers, and leveraging the tsd library.
The TypeScript 5.1 beta is out - here's everything you need to know.
There’s a difference between using TypeScript and knowing TypeScript.
The docs give you a good grasp of the pieces like generic functions, conditional types, and type helpers.
But out in the wild, developers are combining these pieces together into patterns.
Four of the most important patterns to know and use are:
Testing code doesn't need to be typed so strictly, and sometimes tests need to pass the wrong type. I made a library called shoehorn that eases the pain of working with tests in TypeScript by providing a first-class way to pass the wrong type to a function.
The article discusses why TypeScript does not throw an error when a function that is assigned to a variable doesn't match its type. It explains that a function with fewer parameters than its type can still be passed, and this behavior is not restricted to TypeScript but exists in JavaScript as well.
TypeScript 5.0 introduces const type parameters which are useful in preserving the literal types of objects passed to functions.
Updates to TypeScript 5.0 have made their way into Total TypeScript!
Exclude
is a very powerful utility type that can be used in a variety of ways. In this article, I'll show you 9 ways to use it along with code examples.
Using the satisfies keyword is one of four ways to make type assignments in TypeScript. In this article we'll look at examples of when each method should be used.
Understand why TypeScript throws complicated errors by learning how to read them. Errors mirror the structure of the code being compared and can be simplified by changing the way types are assigned.
Learn how to use TypeScript generics on the type level and with functions to improve code readability, type safety, and reduce repetitive code. Use "type helpers" to create new types and generic functions to pass in and return specific types.
Use Zod to validate unknown inputs in your app, whether it's a CLI or a public API, to ensure that data entering your app is safe. Zod can also be used for 'sort of' trusted inputs, like third-party services, to ensure that the data returned is what you expect.
TypeScript's template literal syntax allows developers to manipulate and transform strings in a powerful way. This can be extended using unions, infer, and recursion to handle more complex tasks.
Donny (kdy1 on GitHub) is rewriting TypeScript in Rust hoping to speed up tsc which is slow due to its TypeScript base. In this interview, he explains some of the financial and technical challenges they face with this open-source project.
Let's imagine you're creating a function which sums up an array of objects. Here's one, taken from the Excalidraw codebase:
const sum = <T>(array: readonly T[], mapper: (item: T) => number): number => array.reduce((acc, item) => acc + mapper(item), 0)
Let's look at the type definition. This function takes in:
readonly T[]