TypeScript Tips for React Developers

TypeScript brings type safety to JavaScript, making your React applications more robust and maintainable.

Strong Typing with React Components

Define component props with interfaces:

interface ButtonProps {
  onClick: (e: React.MouseEvent<HTMLButtonElement>) => void;
  children: React.ReactNode;
  variant?: 'primary' | 'secondary';
}

export const Button: React.FC<ButtonProps> = ({
  onClick,
  children,
  variant = 'primary'
}) => {
  return (
    <button onClick={onClick} className={`btn-${variant}`}>
      {children}
    </button>
  );
};

Generics for Reusable Components

Use generics to create flexible, type-safe components:

interface ListProps<T> {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
}

export function List<T extends { id: string | number }>({
  items,
  renderItem,
}: ListProps<T>) {
  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>{renderItem(item)}</li>
      ))}
    </ul>
  );
}

Type-Safe Hooks

Create custom hooks with proper typing:

function useAsync<T>(
  asyncFunction: () => Promise<T>,
  immediate = true,
): {
  status: "idle" | "pending" | "success" | "error";
  value: T | null;
  error: Error | null;
} {
  const [status, setStatus] = React.useState<
    "idle" | "pending" | "success" | "error"
  >("idle");
  const [value, setValue] = React.useState<T | null>(null);
  const [error, setError] = React.useState<Error | null>(null);

  const execute = React.useCallback(async () => {
    setStatus("pending");
    try {
      const response = await asyncFunction();
      setValue(response);
      setStatus("success");
    } catch (err) {
      setError(err instanceof Error ? err : new Error(String(err)));
      setStatus("error");
    }
  }, [asyncFunction]);

  React.useEffect(() => {
    if (immediate) {
      execute();
    }
  }, [execute, immediate]);

  return { status, value, error };
}

Key Takeaways

  1. Define interfaces for props - Ensures components are used correctly
  2. Use generics for reusability - Create flexible, type-safe components
  3. Type your hooks - Prevent bugs in custom hook logic
  4. Leverage union types - Express valid state combinations more clearly

TypeScript might add some boilerplate, but it pays off with fewer runtime errors and better IDE support!