DEV Community

SOVANNARO
SOVANNARO

Posted on

TypeScript Patterns You Should Know for React Development

TypeScript and React are like peanut butter and jelly—a perfect combination that makes development smoother, safer, and more fun! If you're a React developer looking to level up your TypeScript skills, you’re in the right place. Let's dive into some must-know TypeScript patterns that will make your React code more readable, maintainable, and bug-free.


1. Strictly Typed Props with Type Aliases & Interfaces

Ever found yourself debugging a "props undefined" error? Say goodbye to those headaches! TypeScript allows us to define strict types for props, making our components more predictable.

interface ButtonProps {
  label: string;
  onClick: () => void;
}

const Button: React.FC<ButtonProps> = ({ label, onClick }) => {
  return <button onClick={onClick}>{label}</button>;
};
Enter fullscreen mode Exit fullscreen mode

Why use this pattern?

  • Ensures props are passed correctly.
  • Autocomplete helps speed up development.
  • Prevents runtime errors before they happen.

2. Union Types for Conditional Props

Sometimes, components have variations. Instead of making everything optional (which can be a mess!), use union types to create clear prop variations.

type CardProps =
  | { type: 'image'; imageUrl: string; title: string }
  | { type: 'text'; content: string };

const Card: React.FC<CardProps> = (props) => {
  if (props.type === 'image') {
    return <img src={props.imageUrl} alt={props.title} />;
  }
  return <p>{props.content}</p>;
};
Enter fullscreen mode Exit fullscreen mode

Why use this pattern?

  • Makes it impossible to pass incorrect props.
  • Eliminates unnecessary optional fields.
  • Improves clarity and maintainability.

3. Using Generics for Flexible Components

Want a reusable component that works with different data types? Generics to the rescue! They allow you to keep your types dynamic yet strict.

type ListProps<T> = {
  items: T[];
  renderItem: (item: T) => JSX.Element;
};

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

const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
];

<List
  items={users}
  renderItem={(user) => <li key={user.id}>{user.name}</li>}
/>;
Enter fullscreen mode Exit fullscreen mode

Why use this pattern?

  • Increases reusability without sacrificing type safety.
  • Works for any data type.
  • Keeps components flexible yet predictable.

4. Discriminated Unions for Better State Management

Handling multiple states in a component? Instead of juggling multiple boolean flags, use a discriminated union for cleaner state management.

type FetchState =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: string }
  | { status: 'error'; error: string };

const MyComponent = () => {
  const [state, setState] = React.useState<FetchState>({ status: 'idle' });

  if (state.status === 'loading') return <p>Loading...</p>;
  if (state.status === 'error') return <p>Error: {state.error}</p>;
  if (state.status === 'success') return <p>Data: {state.data}</p>;
  return <button onClick={() => setState({ status: 'loading' })}>Fetch Data</button>;
};
Enter fullscreen mode Exit fullscreen mode

Why use this pattern?

  • Removes the need for multiple boolean states.
  • Guarantees all possible states are handled.
  • Improves code clarity and debugging.

5. Type-Safe Context API

Using React Context? Make it type-safe to avoid unnecessary any usage.

type Theme = 'light' | 'dark';

interface ThemeContextProps {
  theme: Theme;
  toggleTheme: () => void;
}

const ThemeContext = React.createContext<ThemeContextProps | undefined>(undefined);

const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const [theme, setTheme] = React.useState<Theme>('light');

  const toggleTheme = () => setTheme((prev) => (prev === 'light' ? 'dark' : 'light'));

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};
Enter fullscreen mode Exit fullscreen mode

Why use this pattern?

  • Prevents undefined errors when accessing context.
  • Provides strong typing for consumers.
  • Improves developer experience with autocomplete.

🚀 Wrapping Up

TypeScript brings superpowers to React development! By using these patterns, you’ll write safer, cleaner, and more maintainable code while avoiding common pitfalls. Whether it’s defining props, managing state, or creating reusable components, TypeScript has your back.

🔗 Let’s connect!
If you found this helpful, consider following me on GitHub 👉 GitHub and supporting my work 👉 Buy Me a Coffee. It means the world! 💙

Happy coding! 🚀✨

Heroku

Build apps, not infrastructure.

Dealing with servers, hardware, and infrastructure can take up your valuable time. Discover the benefits of Heroku, the PaaS of choice for developers since 2007.

Visit Site

Top comments (0)

Heroku

This site is powered by Heroku

Heroku was created by developers, for developers. Get started today and find out why Heroku has been the platform of choice for brands like DEV for over a decade.

Sign Up

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay