TypeScript: The First Encounter

Alicia
3 min readDec 30, 2023

--

Previously, I was working on a project and decided to make it a personal challenge for myself by using TypeScript for it. I came to understand the beauty of it despite the bouts of confusion and cluelessness. In here, I document my learnings with reference to my project.

Learnings

1. Generic Types

In my application, I worked with grids on two separate occasions — display of categories and services. To ensure reusability of components, I had to create a grid component which accepts both category and service types. Depending on the type received, it will render a different output.

First, I created my CardGrid component which accepts type and cards as parameters.

const CardGrid = <T extends CardTypeProps>({
type,
cards,
}: CardGridProps<T>): ReactNode => {
...
}

Next, I specified the type of the card grid component through the syntax <T extends CardTypeProps> where CardTypeProps either holds generic (represents category) or detailed (represents service). In doing so, the card grid component type is constrained to the types listed in CardTypeProps .

type CardTypeProps = 'generic' | 'detailed';

Moving on to the parameters, by denoting CardGridProps<T> , it specifics the expected structure of props that need to be provided to the CardGrid component. Additionally, generic constraints is applied here as well where the type of the cards parameter depends on the type <T> passed in.

type CardGridProps<T extends CardTypeProps> = {
type: T;
cards: CardProps<T>;
};

Conditional types are used to determine the type of the cards parameter. If <T> is generic , CardProps resolves to CardGenericObjectProps; otherwise, it resolves to CardServicesProps.

type CardProps<T extends CardTypeProps> = T extends 'generic'
? CardGenericObjectProps
: CardServicesProps;

Lastly, the use of ReactNode signifies that the CardGrid component returns a ReactNode, which is the type typically returned by React components.

2. Type Annotations vs Type Assertions

After having lost count of the number of times I got hit with this error, I eventually got the hang of it after understanding the difference between type annotations and type assertions.

‘xxx’ is of type ‘unknown’

Type Annotations explicitly specifies the types of variables, parameters and return values using the syntax : Type . When a type which is incompatible is assigned, an error will be thrown.

In the following example, service: CardDetailedFormInputProps explicitly tells TypeScript the expected type of the service object, ensuring that it is recognized with the specified props.

const remainingServices = categoryServices.filter(
(service: CardDetailedFormInputProps) => service.id !== id
);

Type Assertions informs the TypeScript compiler about the type of a value, overwriting the default inference. It uses the syntax value as Type or <Type> .

In the example below, category as CardGenericProps casts the category object to the type CardGenericProps . This is used when TypeScript is unable to infer the type of the variable and I hold more information about the specific type expected.

const remainingCategories = categories.filter(
(category) => (category as CardGenericProps).id !== action.payload!.id
);

3. Index Signatures

Element implicitly has an ‘any’ type because expression of type ‘string’ can’t be used to index type

This error happens when TypeScript is unable to determine if the string used to access the object property exists on the object’s type.

For instance, when I tried accessing the PROTECTED_SERVICE_IDS using category which is a string, TypeScript is unable to verify that the category variable passed in exists in PROTECTED_SERVICE_IDS .

To address this, type assertions can be applied. By specifying the structure of PROTECTED_SERVICE_IDS , the type of the object can be retrieved using keyof typeof .

typeof retrieves the type of categoryProps as {body: [], face: [], ...} .
keyof then extracts the keys inferred from typeof , returning a union of the keys as such 'body' | 'face' | ... .

This ensures that category will always be treated as one of the keys in categoryProps .

const categoryProps = {
body: [],
face: [],
hair: [],
'hair removal': [],
nail: [],
};

type CategoryKey = keyof typeof categoryProps;

const selectedCategory: string[] =
PROTECTED_SERVICE_IDS[category as CategoryKey];

--

--