DEV Community

Anupam Chakrawarti
Anupam Chakrawarti

Posted on

Use TypeScript + React effectively

NotByAI

Using TypeScript with React is a must in 2025. And with AI tools running rampant, it is even more important to know how to properly use TypeScript in your React project.

The AI often generates slop implementation because of its vast slopy training data and with time it will get trained on the very slop it generates.

With that note, today I share with you 5 ways to use TypeScript in your React project:

1. Discriminated Union Props with Intersection


// Bad ❌
type ButtonProps = {
    variant: "label" | "no-label";
    label?: string;
    colorCss: string;
};

// Good ✅
type ButtonProps = ({variant: "no-label"} | {variant: "label"; label: string}) & {colorCss: string};

function Button(props: ButtonProps) {
    if (props.variant === "no-label") return <button>No title</button>;
    return <button>{props.title}</button>;
};

Enter fullscreen mode Exit fullscreen mode

2. Discriminated Union in useState


// Bad ❌

export function UserList() {
  const [status, setStatus] = useState({
    state: "loading",
    error: new Error(),
  });

  async function fetchUsers() {}

  useEffect(() => {
    setStatus((prev) => ({ ...prev, state: "loading" }));
    try {
      fetchUsers();
      setStatus((prev) => ({ ...prev, state: "loaded" }));
    } catch (err) {
      if (err instanceof Error) {
        setStatus({ state: "error", error: err });
      }
    }
  }, []);

  {
    status.state === "loading" ? <Loading /> : null;
  }
  {
    status.state === "error" ? <ShowError error={status?.error} /> : null;
  }
  {
    status.state === "loaded" ? <Content /> : null;
  }
}

Enter fullscreen mode Exit fullscreen mode

// Good ✅

export function UserList() {
  type Status =
    | { state: "loading" }
    | { state: "loaded" }
    | { state: "error"; error: Error };

  const [status, setStatus] = useState<Status>({
    state: "loading",
  });

  async function fetchUsers() {}

  useEffect(() => {
    setStatus({ state: "loading" });
    try {
      fetchUsers();
      setStatus({ state: "loaded" });
    } catch (err) {
      if (err instanceof Error) {
        setStatus({ state: "error", error: err });
      }
    }
  }, []);

  {
    status.state === "loading" ? <Loading /> : null;
  }
  {
    status.state === "error" ? <ShowError error={status?.error} /> : null;
  }
  {
    status.state === "loaded" ? <Content /> : null;
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Extracting Keys and Values as Type from an Object


const RESPONSE_CODES = { 200: "Ok", 201: "Created", 202: "Accepted", 204: "No Content", 301: "Moved Permanently", 302: "Found", 304: "Not Modified", 400: "Bad Request", 401: "Unauthorized", 403: "Forbidden", 404: "Not Found", 405: "Method Not Allowed", 409: "Conflict", 500: "Internal Server Error", 501: "Not Implemented", 502: "Bad Gateway", 503: "Service Unavailable", 504: "Gateway Timeout" } as const;

type ResponseCodes = typeof RESPONSE_CODES;
type StatusCode = keyof ResponseCodes;
type StatusMessage = ResponseCodes[StatusCode];

Enter fullscreen mode Exit fullscreen mode

4. Reusable components using ComponentProps


// Bad ❌
interface InputFieldProps {
    value: string;
};

// Good ✅
import { ComponentProps } from "react";

interface InputFieldProps extends ComponentProps<"input"> {
    value: string;
};

function InputField({value, ...rest}: InputFieldProps) {
    return <input value={value} {...rest} />;
};
Enter fullscreen mode Exit fullscreen mode

5. Autocomplete with satisfies + ComponentProps


import { ComponentProps, PropsWithChildren } from "react";

type ButtonVariants = Record<string, ComponentProps<"button"> & {"data-testid": string}>

// Bad ❌
const buttonVariants: ButtonVariants = {
    submit: {
        type: "submit",
        className: "bg-green-300",
        "data-testid": "submit-btn"         
    },
    next: {
        type: "button",
        className: "bg-blue-300",
        "data-testid": "next-btn"       
    },
    reset: {
        type: "reset",
        className: "bg-orange-300",
        "data-testid": "reset-btn"      
    },
    delete: {
        type: "button",
        className: "bg-red-300",
        "data-testid": "delete-btn"         
    }
};

// Good ✅
const buttonVariants = {
//  ...
    delete: {
        type: "button",
        className: "bg-red-300",
        "data-testid": "delete-btn"         
    }
} satisfies ButtonVariants;

type ButtonProps = PropsWithChildren & {variant: keyof typeof buttonVariants};

function Button({children, ...rest}: ButtonProps) {
    return <button {...buttonVariants[rest.variant]}>{children}</button>
};

// Now variant will infer the types (delete, submit, next, reset) on autocomplete

<Button variant="delete">Delete record</Button>

Enter fullscreen mode Exit fullscreen mode

Top comments (0)