React TypeScript Cheatsheet

website·Indexed 29 days ago

https://react-typescript-cheatsheet.netlify.app/docs/basic/setup/

A comprehensive reference for using TypeScript with React. Covers typing for function and class components, hooks (useState, useCallback), refs, context, and portals. Includes guidance on React 19+ patterns such as Ref as a Prop, Concurrent React APIs (Suspense, use), and advanced patterns for wrapping HTML elements using ComponentProps and Generic Components.

Tokens
13.6K
Snippets
71
Records
84
Agent score
75%

What's inside React TypeScript Cheatsheet

  1. Use ReactNode with Async Server Components

    Server Components can be async and return a Promise<ReactNode>. React unwraps this promise using the nearest <Suspense> boundary. You can also pass a Promise<ReactNode> directly as children to implement streaming patterns.
    // Server Component returning a Promise
    async function UserProfile({ userId }: { userId: string }) {
      const user = await fetchUser(userId);
      return <p>{user.name}</p>;
    }
    
    function Page() {
      return (
        <Suspense fallback={<p>Loading…</p>}>
          <UserProfile userId="42" />
        </Suspense>
      );
    }
    
    // Passing a promise directly as children
    function Page() {
      const userPromise = fetchUser(); // Promise<ReactNode>
      return <Suspense fallback={<Loading />}>{userPromise}</Suspense>;
    }
  2. Type a style prop on a custom component

    When creating a component that accepts a style prop to be forwarded to an HTML element, use the CSSProperties type to ensure compatibility with standard HTML element style props.
    import { CSSProperties, ReactNode } from "react";
    
    type BoxProps = {
      style?: CSSProperties;
      children?: ReactNode;
    };
    
    function Box({ style, children }: BoxProps) {
      return <div style={{ padding: 16, ...style }}>{children}</div>;
    }
  3. Handle refs in Generic Components

    Generic components often require manual ref handling because automatic type inference is limited. The simplest approach is to pass the ref as a custom prop (e.g., mRef). For advanced cases requiring forwardRef behavior with generics, you may need to redeclare the forwardRef module or use a call signature.
    interface ClickableListProps<T> {
      items: T[];
      onSelect: (item: T) => void;
      mRef?: React.Ref<HTMLUListElement> | null;
    }
    
    export function ClickableList<T>(props: ClickableListProps<T>) {
      return (
        <ul ref={props.mRef}>
          {props.items.map((item, i) => (
            <li key={i}>
              <button onClick={() => props.onSelect(item)}>Select</button>
              {item}
            </li>
          ))}
        </ul>
      );
    }
  4. Narrow Component Types based on Props

    To create a component that renders different HTML elements based on props (e.g., rendering an <a> if href is present, otherwise a <button>), use function overloading or type guards. This ensures that props specific to one element (like disabled for buttons) cannot be used when the component is acting as another element (like an anchor).
    type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
      href?: undefined;
    };
    
    type AnchorProps = React.AnchorHTMLAttributes<HTMLAnchorElement> & {
      href?: string;
    };
    
    // Overload signature
    type Overload = {
      (props: ButtonProps): React.JSX.Element;
      (props: AnchorProps): React.JSX.Element;
    };
    
    const hasHref = (props: ButtonProps | AnchorProps): props is AnchorProps =>
      "href" in props;
    
    const Button: Overload = (props: ButtonProps | AnchorProps) => {
      if (hasHref(props)) return <a {...props} />;
      return <button {...props} />;
    };
  5. Set default props for class components

    Class components still support static defaultProps. The recommended TypeScript pattern is to define the props with the defaulted keys as required. React's LibraryManagedAttributes (applied automatically by JSX) will make these props optional at the call site.
    type GreetProps = {
      age: number;
    };
    
    class Greet extends React.Component<GreetProps> {
      static defaultProps = {
        age: 21,
      };
    
      render() {
        return <div>Hello, I am {this.props.age}</div>;
      }
    }
    
    // Type-checks: `age` is optional at the call site
    const el = <Greet />;
  6. Accept a ref as a prop in React 19

    In React 19, ref is a regular prop on function components, and forwardRef is no longer required. Use the Ref<T> union type for the prop definition to ensure the component accepts both RefObject and RefCallback styles.
    import { Ref } from "react";
    
    type FancyInputProps = {
      ref?: Ref<HTMLInputElement>;
      placeholder?: string;
    };
    
    function FancyInput({ ref, placeholder }: FancyInputProps) {
      // Pass the ref directly to the element
      return <input ref={ref} placeholder={placeholder} className="fancy" />;
    }
    
    // Example of forwarding the ref to a nested element
    type LabelledInputProps = {
      label: string;
      ref?: Ref<HTMLInputElement>;
    };
    
    function LabelledInput({ label, ref }: LabelledInputProps) {
      return (
        <label>
          {label}
          <input ref={ref} />
        </label>
      );
    }
  7. Handle loading states with the Suspense component

    The Suspense component allows you to declaratively define a fallback UI to display while a child component is waiting for a resource, such as a promise unwrapped with use(), a lazy-loaded component, or a server-streamed boundary. The SuspenseProps are typed as { children?: ReactNode; fallback?: ReactNode }, where the fallback can be any ReactNode or null.
    import { Suspense } from "react";
    
    const UserProfile = ({ userPromise }: { userPromise: Promise<User> }) => {
      const user = use(userPromise);
      return <p>Hello, {user.name}!</p>;
    };
    
    const App = ({ userPromise }: { userPromise: Promise<User> }) => (
      <Suspense fallback={<p>Loading...</p>}>
        <UserProfile userPromise={userPromise} />
      </Suspense>
    );
  8. Use Discriminated Unions for type-safe event handling

    Discriminated Unions allow you to narrow types based on a specific literal key (the 'discriminant'). To successfully narrow the type, the check must be performed on the value of the discriminant key, not on the type of a property using typeof.
    type UserTextEvent = {
      type: "TextEvent";
      value: string;
      target: HTMLInputElement;
    };
    type UserMouseEvent = {
      type: "MouseEvent";
      value: [number, number];
      target: HTMLElement;
    };
    type UserEvent = UserTextEvent | UserMouseEvent;
    
    function handle(event: UserEvent) {
      if (event.type === "TextEvent") {
        event.value; // Narrowed to string
        event.target; // Narrowed to HTMLInputElement
        return;
      }
      event.value; // Narrowed to [number, number]
      event.target; // Narrowed to HTMLElement
    }
  9. Implement Polymorphic Components with the 'as' prop

    Polymorphic components allow the caller to specify which element or component should be rendered using a prop (commonly named as). Use React.ElementType to type the as prop to support any valid HTML element or React component.
    function PassThrough(props: { as: React.ElementType<any> }) {
      const { as: Component } = props;
      return <Component />;
    }
  10. Handle React Context without a default value

    When a meaningful default value is unavailable, initialize the context with null and include null in the type definition. To avoid repetitive null checks (optional chaining) in components, create a custom hook that performs a runtime check and throws a descriptive error if the context is missing. This ensures the returned value is non-null for TypeScript.
    import { createContext, use } from "react";
    
    interface CurrentUserContextType {
      username: string;
    }
    
    // Initialize with null
    const CurrentUserContext = createContext<CurrentUserContextType | null>(null);
    
    // Custom hook for type-safe consumption
    const useCurrentUser = () => {
      const currentUserContext = use(CurrentUserContext);
    
      if (!currentUserContext) {
        throw new Error(
          "useCurrentUser has to be used within <CurrentUserContext>",
        );
      }
    
      return currentUserContext;
    };
    
    const MyComponent = () => {
      const currentUser = useCurrentUser();
      // currentUser is now typed as CurrentUserContextType (not null)
      return <p>Username: {currentUser.username}.</p>;
    };
  11. Extract props from an existing React component

    When wrapping a React component where you don't have direct access to the prop definitions, you can use a generic utility type to infer the props from the component type.
    // Utility to extract props from a component type
    declare type $ElementProps<T> = 
      T extends React.ComponentType<infer Props>
        ? Props extends object
          ? Props
          : never
        : never;
    
    const Box = (props: React.CSSProperties) => <div style={props} />;
    
    const Card = ({ title, children, ...props }: { title: string } & $ElementProps<typeof Box>) => (
      <Box {...props}>
        {title}: {children}
      </Box>
    );
    
  12. Create Generic React Components

    Generic components allow a component to work with multiple types while maintaining type safety for props and internal state. When using arrow functions in TSX, you must use <T extends unknown> or <T,> to prevent the compiler from confusing the generic declaration with a JSX tag.
    import { ReactNode, useState } from "react";
    
    interface Props<T> {
      items: T[];
      renderItem: (item: T) => ReactNode;
    }
    
    const List = <T extends unknown>(props: Props<T>) => {
      const { items, renderItem } = props;
      const [state, setState] = useState<T[]>([]);
      return (
        <div>
          {items.map(renderItem)}
          <button onClick={() => setState(items)}>Clone</button>
          {JSON.stringify(state, null, 2)}
        </div>
      );
    };