Type-Safe React Components in TypeScript

3 min read

The Astro logo with the word One.

TypeScript brings typed goodness to React. Writing components in TS provides end-to-end catch safety nets. It takes a bit more work upfront, but prevents entire classes of runtime errors later on.

In this article, we’ll explore how to author typed React component functions in TS.

Functional Components

Here’s a basic functional component in JSX:

// JavaScript
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>; 
}

And the TypeScript equivalent with types:

// TypeScript
interface WelcomeProps {
  name: string;
}

const Welcome: React.FC<WelcomeProps> = ({name}) => {
  return <h1>Hello, {name}</h1>;
} 

React.FC types the component. <WelcomeProps> defines the props shape. Clean and simple.

We can also separate the props interface:

interface WelcomeProps {
  name: string;
}

const Welcome: React.FC<WelcomeProps> = ({name}) => {
  // ...
}

Let’s explore additional patterns and best practices.

Typing Props

Use interfaces to describe props shapes:

interface Props {
  message: string;
  count: number;
  disabled: boolean;
  names: string[];  
  handler: () => void;
}

const MyComponent: React.FC<Props> = ({message, count}) => {
  // ...
}

This enforces only valid props get passed down. Else TypeScript will complain.

You can also use PropsWithChildren to allow passing children:

interface Props {
  // ...
}

const MyComponent: React.FC<PropsWithChildren<Props>> = ({children}) => {
  return <div>{children}</div>
}

Optional Props

For optional props, use the ? modifier:

interface Props {
  message: string;
  count?: number;
}

Or provide a default prop value:

const MyComponent: React.FC<Props> = ({count = 0}) => {
  // ...
}

Union Types

For props that can accept multiple types, use unions:

interface Props {
  size: 'small' | 'medium' | 'large'; 
}

This ensures only valid sizes get passed in.

Component Generics

We can make components more reusable via generics:

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

function List<T>(props: ListProps<T>) {
  //...
}

Now List accepts any prop types generically.

Typing Component State

Here’s how to add types to state:

interface State {
  count: number;
  name: string;
}

class MyComponent extends React.Component<{}, State> {
  state: State = {
    count: 0,
    name: '' 
  }
}

React.Component<{}, State> types the component class.

Typing Hooks

And typing hooks:

interface State {
  count: number;
  name: string; 
}

function MyComponent() {
  const [state, setState] = useState<State>({
    count: 0,
    name: ''
  })
}

Again useState<State> types the hook generically.

Typing Functions

To type component handlers, use:

const MyComponent: React.FC<Props> = ({ onClick }) => {

  const handleClick = (): void => {
    // Typed! 
  }

  return <button onClick={handleClick}>Click</button>
}

(): void types the function signature.

Typing Event Handlers

For event handlers, you can type the event:

const MyComponent = () => {

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
    e.persist(); 
  }

  return <input onChange={handleChange} />;

}

Typing onChange and e catches errors.

Catching Bugs

Here are some examples of bugs TypeScript would catch:

  • Passing invalid prop types
  • Using undefined state
  • Calling props/state incorrectly
  • Incorrect hook types
  • Assigning wrong function types
  • Passing wrong event types

Adding light types provides end-to-end protection.

When To Use Types

Start without types to move fast. Add selectively when:

  • Component grows beyond simple cases
  • Finding runtime errors
  • Planning to reuse component

Avoid overengineering with types. Add judiciously to balance productivity and safety.

Benefits of TypeScript

TypeScript pays dividends through:

  • Catching bugs before runtime
  • Self-documenting code
  • Easier refactors
  • More reusable components
  • Better tooling like intellisense

It requires more coding overhead. But reduces debugging and maintaining apps long-term.

Key Takeaways

  • Use React.FC and interfaces to type components
  • Generics make reusable typed components
  • Type state, hooks, handlers and events
  • Add types judiciously to balance productivity
  • TypeScript prevents bugs and eases maintenance

Typed components take a bit more work. But pay off exponentially in long term productivity and resilience.

Take Your Skills to the Next Level

If you enjoyed this article, you may also like these related pieces:

Wir sind dein digitaler Problemlöser

Du brauchst Unterstützung bei der Entwicklung, dem Hosting oder der Optimierung deiner Webanwendung oder deines Online-Shops? Oder suchst du Hilfe bei der Erstellung deiner Marketingstrategie?

Als Netzwerk aus Freelancern und Agenturen haben wir für jede Aufgabe den passenden Experten für dich!

Wir sind dein One-Stop-Shop für alles Digitale. Mit über 20 Jahren Erfahrung in den Bereichen Webentwicklung, SEO, Cloud und SaaS wissen wir, worauf es ankommt.

Unser Motto: Wir lieben es, technische Probleme zu lösen und digitale Lösungen auf die Beine zu stellen. Dabei gehen wir immer mit der Zeit und setzen auf die neuesten Technologien. Also worauf wartest du noch? Lass uns ins Gespräch kommen und dein nächstes Digitalprojekt zum Erfolg führen!

Bild von einem Büro mit Küche, mit Laptop, Kaffee und Notizbuch