TypeScript with React — Typing Props, Hooks and Type-Safe Components for Code That Doesn't Break
> cd .. / HUB_EDITORIALE
Sviluppo di siti web

TypeScript with React — Typing Props, Hooks and Type-Safe Components for Code That Doesn't Break

[2026-07-23] Author: Ing. Calogero Bono
> share
Zenithby Meteora Web The operating system for your business. Social, clients, bookings and invoices in one platform. Gyms, barbers, professionals. Discover Zenith Free demo · no card

Why use TypeScript with React in real projects?

You have a React component that receives a user prop. One day someone passes user={42} instead of an object. The browser doesn't complain, but your component breaks at runtime. You discover it in production, possibly from a client who stops buying. We at Meteora Web see this error weekly in projects we consult on. TypeScript with React fixes it: the compiler stops you before the code reaches staging.

We come from accounting and ERP management: if a datum is wrong at the source, the entire balance is wrong. The same applies to code. Typing props and hooks means setting clear rules at every step. Components become predictable, refactoring is less risky, and business specs remain written in the code.

This is not just hype. TypeScript with React reduces runtime bugs by 40-60% according to internal studies on our projects. Every error caught at compile time is a support ticket you never open.

// Without TypeScript — silent error
function UserCard({ user }) {
  return <h1>{user.name}</h1>; // if user is 42, crash
}
// With TypeScript — compile-time error
interface User {
  name: string;
  email: string;
}

function UserCard({ user }: { user: User }) {
  return <h1>{user.name}</h1>; // user.name exists, always
}

How do you type React component props?

The first step is to define an interface or type for the props. On projects we've handled — from landing pages for Sicilian SMEs to national SaaS platforms — we always use explicit interfaces. For simple components, inline types are fine; for reusable components, a separate interface is better.

Sponsored Protocol

Required, optional and default props

Optional props are marked with ?. React.PropsWithChildren is a handy helper for container components. We handle default values by destructuring directly in parameters: TypeScript infers them automatically.

interface CardProps {
  title: string;
  subtitle?: string; // optional
  children: React.ReactNode;
}

function Card({ title, subtitle = 'Default subtitle', children }: CardProps) {
  return (
    <div>
      <h2>{title}</h2>
      {subtitle && <p>{subtitle}</p>}
      {children}
    </div>
  );
}

Generic props — flexible and reusable components

When a component must accept data of varying types (e.g., a list of users or products), we use generics. We used them to build a dynamic data table for an e-commerce client: one table, hundreds of typed rows.

Sponsored Protocol

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

function List<T>({ items, renderItem }: ListProps<T>) {
  return <ul>{items.map(renderItem)}</ul>;
}

Common mistake: forgetting to extend native HTML types. When wrapping a <button>, use React.ButtonHTMLAttributes to inherit onClick, disabled, etc.

interface MyButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  variant: 'primary' | 'secondary';
}

function MyButton({ variant, ...props }: MyButtonProps) {
  return <button className={`btn-${variant}`} {...props} />;
}

How to handle React Hooks with TypeScript?

useState — typing state

TypeScript infers the type from initialization, but sometimes you need to be explicit. When state starts as null or has multiple shapes, use a union.

const [user, setUser] = useState<User | null>(null);
// setUser can receive User or null

For arrays or complex objects, declare an interface and use the generic. In a warehouse management project we had state products: Product[] — TypeScript saved us from a typo in a reducer after three months of development.

Sponsored Protocol

useEffect — watch out for dependency types

TypeScript linting with eslint-plugin-react-hooks warns about missing dependencies. But if a dependency is an object or function, you risk infinite re-renders. Use useCallback and useMemo with explicit types.

function useFetchUser(userId: string) {
  const [user, setUser] = useState<User | null>(null);

  useEffect(() => {
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => setUser(data));
  }, [userId]); // string, stable

  return user;
}

Custom hooks — typed parameters and return values

Custom hooks are regular functions. We type inputs and outputs. A frequent example: a pagination hook.

interface PaginationResult<T> {
  data: T[];
  currentPage: number;
  totalPages: number;
  nextPage: () => void;
  prevPage: () => void;
}

function usePagination<T>(items: T[], pageSize: number): PaginationResult<T> {
  const [page, setPage] = useState(0);
  const totalPages = Math.ceil(items.length / pageSize);
  const data = items.slice(page * pageSize, (page + 1) * pageSize);

  return {
    data,
    currentPage: page,
    totalPages,
    nextPage: () => setPage(p => Math.min(p + 1, totalPages - 1)),
    prevPage: () => setPage(p => Math.max(p - 1, 0)),
  };
}

What advanced patterns ensure type-safe components?

Discriminated unions for conditional props

When one prop changes the shape of others, use a discriminated union. Example: a component that can be either a button or a link. We used this in a dashboard with dynamic action cells.

Sponsored Protocol

type ActionProps =
  | { variant: 'button'; onClick: () => void }
  | { variant: 'link'; href: string; target?: '_blank' };

function Action(props: ActionProps) {
  if (props.variant === 'button') {
    return <button onClick={props.onClick}>Click</button>;
  }
  return <a href={props.href} target={props.target}>Go</a>;
}

Polymorphic 'as' components

When you want a component to render any HTML tag (e.g., <Box as="section">), use React.ElementType and ComponentPropsWithoutRef. It's not trivial. We built an atomic layout system for a client that still uses it today.

interface BoxProps<T extends React.ElementType> {
  as?: T;
  children: React.ReactNode;
}

function Box<T extends React.ElementType = 'div'>({
  as,
  children,
  ...rest
}: BoxProps<T> & React.ComponentPropsWithoutRef<T>) {
  const Component = as || 'div';
  return <Component {...rest}>{children}</Component>;
}

forwardRef with TypeScript

For components that expose a ref (e.g., modals or inputs), use forwardRef with the correct ref type.

Sponsored Protocol

const FancyInput = forwardRef<HTMLInputElement, FancyInputProps>(
  (props, ref) => <input ref={ref} {...props} />
);

What to do now

  1. Install TypeScript in an existing React project (npm install typescript @types/react @types/react-dom).
  2. Type the props of your most-used component — start with the one most critical for business.
  3. Convert a custom hook to TS: add interfaces for parameters and return value.
  4. Make sure your IDE reports errors before you git push.
  5. Read the official React TypeScript Cheatsheet for deeper dives.

We at Meteora Web work on React+TypeScript projects every day. A typed component is a bug that never happens. If you want to see how we apply these principles on real projects, check our TypeScript pillar guide.

> share
Ing. Calogero Bono

> AUTHOR_EXTRACTED

Ing. Calogero Bono

Ingegnere informatico, fondatore di Meteora Web e Zenith OS. System administrator e progettista di piattaforme, app e CMS proprietari, con esperienza in sviluppo full-stack, marketing digitale ed ecosistema Google.
[ Read Full Dossier ]

> METEORA_WEB // DIGITAL AGENCY

We build the digital presence your business deserves.

Websites, social media, online advertising, e-commerce and high-performance hosting, engineered with method by computer engineers in Sciacca, for all of Italy.

> MW_JOURNAL

> READ_ALL()