React hooks were introduced in February 2019 as a way to enhance code clarity. While we’ve previously explored React hooks, this article focuses on their interaction with TypeScript.
Before hooks, React components could be categorized into two types:
- Classes: Responsible for managing state.
- Functions: Entirely determined by their received props.
This naturally led to building intricate container components using classes and straightforward presentational components using pure functions.
Understanding React Hooks
Container components are responsible for managing state and handling server requests, referred to as side effects in this article. The state is then passed down to the container’s children through props.

However, as the codebase expands, functional components often evolve into container components.
Although upgrading a functional component into a smarter one isn’t overly complex, it’s a tedious and time-consuming process. Additionally, maintaining a strict separation between presenters and containers is no longer considered crucial.
Hooks offer a solution by handling both aspects, resulting in more consistent code and numerous advantages. Here’s an illustration of incorporating a local state into a component managing a quotation signature:
| |
A significant advantage emerges—while coding with TypeScript was efficient in Angular but cumbersome in React, combining React hooks with TypeScript provides a seamless experience.
Challenges of TypeScript with Traditional React
Developed by Microsoft, TypeScript aligned with Angular’s approach while React created Flow, which is gradually losing momentum. Writing React classes using basic TypeScript was cumbersome as developers had to define both props and state, even with overlapping keys.
Consider a simple domain object. Let’s create a quotation app, utilizing a Quotation type, managed within components using state and props. A Quotation can be created and its status can transition to signed or unsigned.
| |
Now, let’s assume the QuotationPage needs to retrieve an id from the server, for instance, the 678th quotation generated by the company. In this scenario, QuotationProps doesn’t possess this essential information—it’s not directly wrapping a complete Quotation. We need to expand the QuotationProps interface:
| |
We duplicate all attributes except id into a new type. This resembles the old Java practice of creating numerous DTOs. To address this, we’ll leverage advanced TypeScript techniques.
Advantages of TypeScript with Hooks
Hooks allow us to eliminate the QuotationState interface by dividing it into two distinct state components.
| |
This state splitting eliminates the need for new interfaces. Local state types are often inferred from the initial state values.
Since components using hooks are functions, we can represent the same component using the FC<P> type from the React library. The function explicitly declares its return type, including the props type.
| |
Clearly, employing TypeScript with React hooks is more straightforward than with React classes. Considering the code safety provided by strong typing, adopting TypeScript is recommended for new projects using hooks. Conversely, projects incorporating TypeScript should leverage hooks.
While there are reasons to avoid TypeScript, with or without React, its adoption strongly suggests utilizing hooks.
TypeScript Features Tailored for Hooks
In the previous example, the number attribute within QuotationProps lacks context regarding its actual meaning.
TypeScript offers various utility types to address this. Three of these prove particularly useful with React by minimizing interface definitions:
Partial<T>: Represents any subset ofT’s keys.Omit<T, 'x'>: Includes all keys ofTexcept for'x'.Pick<T, 'x', 'y', 'z'>: Selects only the keys'x','y', and'z'fromT.

In our case, Omit<Quotation, 'id'> helps omit the id from Quotation. We can define a new type instantly using the type keyword.
While Partial<T> and Omit<T> are absent in languages like Java, they prove valuable in front-end development, especially with Forms, simplifying type declarations.
| |
We now have a Quotation without an id. This suggests the possibility of defining Quotation and PersistedQuotation with an inheritance relationship (extends). Additionally, we can address recurring issues related to if checks and undefined values. However, the naming convention for the variable (quotation) representing a partial object is a topic for another discussion.
Importantly, we’ve ensured that we won’t accidentally spread an object assuming it has a number key. However, Partial<T> doesn’t provide the same level of guarantee, so use it judiciously.
Pick<T, ‘x’|’y’> offers another approach to define types without creating new interfaces. For components simply editing the Quotation title:
| |
Alternatively:
| |
To be clear, this preference for concise type definitions doesn’t stem from laziness. Interfaces are used for clearly defining Domain concepts, while these utility functions ensure local code correctness without unnecessary verbosity. The reader can readily identify Quotation as the canonical interface.
Further Benefits of React Hooks
The React team has consistently approached React from a functional perspective. Classes were initially used to enable components to manage their own state, and hooks now provide a mechanism for functions to achieve the same.
| |
This scenario highlights a safe and appropriate use of Partial.
While a function might be executed multiple times, the associated useReducer hook is created only once.
By extracting the reducer function outside the component, the code can be modularized into independent functions instead of being coupled within a class.
This significantly improves testability—functions can focus on JSX, behavior, business logic, etc.
Higher Order Components are mostly unnecessary, and the Render props pattern becomes simpler with functions.
Code readability is enhanced as the codebase transitions from a mix of classes, functions, and patterns to a more unified flow of functions. However, naming these functions effectively can be challenging without the context of an object.
TypeScript Remains Rooted in JavaScript
JavaScript’s flexibility allows for dynamic code manipulation. TypeScript retains this flexibility through features like keyof for working with object keys. While type unions enable complex and potentially unmaintainable structures (which are best avoided), type aliases allow treating strings as UUIDs.
However, this flexibility comes with the responsibility of ensuring null safety. It’s crucial to enable the "strict":true option in the tsconfig.json file at the project’s outset to avoid extensive refactoring.
The level of typing in code is a matter of debate, with options ranging from explicit typing to relying on type inference. This depends on linter configurations and team preferences.
Moreover, runtime errors are still possible! TypeScript, being simpler than Java, avoids the complexities of covariance and contravariance in Generics.
In this Animal/Cat example, an Animal list is considered equivalent to a Cat list. However, this equivalence is based on the initial declaration, not on subsequent modifications. Adding a Duck to the Animal list invalidates the assumption that it’s a list of Cats.
| |
TypeScript employs a bivariant approach for generics, simplifying adoption for JavaScript developers. With proper variable naming, adding a duck to a listOfCats becomes less likely.
There’s also a proposal to introduce in and out contracts for explicit covariance and contravariance handling.
Conclusion
Returning to Kotlin, a robust typed language, highlighted the complexity of correctly typing complex generics. TypeScript avoids this complexity due to its duck typing and bivariant approach, albeit with other trade-offs. While Angular, designed with TypeScript in mind, might not have posed significant typing challenges, React classes did.
TypeScript emerged as a prominent player in 2019, gaining traction in React and the back-end world with Node.js, thanks to its ability to type existing libraries using relatively straightforward declaration files. This has overshadowed Flow, despite some efforts to all the way with ReasonML.
The combination of hooks and TypeScript now feels more enjoyable and productive than Angular, a sentiment that would have been surprising just six months ago.