TypeScript + React Tips & Tricks

RocíoRocíoOctober 31, 2023
TypeScript + React Tips & Tricks

TypeScript + React: Tips & Tricks

The advantages of adding TypeScript to React have become impossible to ignore. Today, most React projects choose to type their components and hooks to prevent errors, facilitate code refactoring, and generally improve the development experience.

In this post, I'll share some TypeScript + React recipes that I've collected over the last few years working with this stack.

Generic Functional Components

import { ReactNode, useState } from "react";

interface Props<T> {
  item: T;
  onClickRow: (item: T) => ReactNode;
}

const Row = <T,>(props: Props<T>) => {
  const { item, onClickRow } = props;
  return (
    <div>
      {JSON.stringify(item, null, 2)}
      <button onClick={() => onClickRow(item)}>Click me</button>
    </div>
  );
};

Usage:

<Row<Address> item={address} onClick={onAddressClick} />

This component accepts any type of item as a prop and calls the onClickRow handler, whose parameter is of the same type as item.

Note the <T, > before the function definition. You can't simply use <T> because the TSX parser doesn't know if it's a JSX tag or a generic component declaration (Explanation here).

With this, you have a generic functional component that accepts a type and calculates the type of its props based on the type passed as a parameter when invoking the component.

Limited Generic Components

type RequireFieldsInRow = { date: string };

type Props<T> = {
  row: T;
};

export const DateColumn = <T extends RequireFieldsInRow>({
  row: { date },
}: Props<T>) => {
  return <div>{formatDate(date)}</div>;
};

This is a generic component that renders a date column. The type it accepts as a parameter can be any object that extends RequireFieldsInRow, that is, any object with a date property of type string.

In this way, we narrow the parameterizable type, limiting the types that are accepted.

Typing a ContextProvider

type State = { isDarkMode: boolean };

type MyContextType = {
  state: State;
  setState: React.Dispatch<React.SetStateAction<State>>;
};

const MyContext = React.createContext<MyContextType | undefined>(undefined);

function MyContextProvider({ children }: { children: React.ReactNode }) {
  const [state, setState] = React.useState({ isDarkMode: false });
  const value = { state, setState };
  return <MyContext.Provider value={value}>{children}</MyContext.Provider>;
}

Here we see how the call to createContext does not carry default values, as we want the value to be initialized directly in the Provider (Explanation here).

Typing children

type Props = {
  children1: JSX.Element; // ❌ Bad, doesn't accept arrays
  children2: JSX.Element | JSX.Element[]; // 🤔 Meh, doesn't accept strings
  children3: React.ReactChildren; // ❌ Not a type, it's a utility
  children4: React.ReactChild[]; // ✅ Better, accepts arrays
  children: React.ReactNode; // ✅ BEST, accepts everything
  style?: React.CSSProperties; // Extra: for passing style properties
};

React.ReactNode is the best type, as it accepts any type of children that React can render. More details at: TypeScript + React Cheatsheet.

Typing props

type Props = {
  onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
  onClick(event: React.MouseEvent<HTMLButtonElement>): void;
  style?: React.CSSProperties;
};

Some recurring types in props: event handlers and style properties. More at: TypeScript + React Cheatsheet.

Asserting Object Properties

const { values } = useForm<CreateFormPayload | EditFormPayload>(…);
const isEdit = hasOwnProperty(values, 'id') && !!values.id;

export function hasOwnProperty<MyObject extends unknown, MyProperty extends PropertyKey>(
  object: MyObject,
  property: MyProperty,
): object is MyObject & Record<MyProperty, unknown> {
  return Object.prototype.hasOwnProperty.call(object, property);
}

More details at: Typescript hasOwnProperty.

Repository of tsconfig Bases

If you want to explore different tsconfig configurations for different environments (node, react, react-native, etc.), here's a good resource:

Repository of tsconfig bases.

And that's all, my friend. 🚀