DEV Community

Ben McMahen
Ben McMahen

Posted on • Originally published at benmcmahen.com on

A Beginners Guide to using Typescript with React

Having spent the last few months developing React applications and libraries using Typescript, I thought I’d share some of the things I’ve learned along the way. These are the patterns I use with Typescript and React about 80% of the time.

Is it worth learning Typescript for developing React applications? Absolutely. I’ve found that having robust typing has led to more reliable code and faster iteration especially within a larger codebase. You’re likely to be frustrated at first, but as you work through it you’ll find that the minimal extra boilderplate is very much worth it.

And if you get stuck on something, remember that you can always type something as any. Any is your friend!

Let’s get to the examples.

Your basic react component with typescript

So what does a standard react component look like with typescript? Let’s compare it to a standard javascript react component.

import React from 'react'
import PropTypes from 'prop-types'

export function StandardComponent({ children, title = 'Dr.' }) {
  return (
    <div>
      {title}: {children}
    </div>
  )
}

StandardComponent.propTypes = {
  title: PropTypes.string,
  children: PropTypes.node.isRequired,
}

And now the typescript version:

import React, { ReactNode } from 'react'

export type StandardComponentProps = {
  title?: string;
  children: ReactNode;
}

export function StandardComponent({
  children,
  title = 'Dr.',
}: StandardComponentProps) {
  return (
    <div>
      {title}: {children}
    </div>
  )
}

Pretty similar, eh? We’ve replaced our propTypes with a typescript type. Our title prop remains optional, while a children prop is required. We’ve exported our type in case another component needs reference to it.

Extending standard HTML attributes

If we want the parent component to be able to provide additional typed div attributes, such as aria-hidden, style, or className we can either define these in our type or we can extend a built in type. In the example below, we are saying that our component accepts any standard div props in addition to title and children.

import * as React from 'react'

export type SpreadingExampleProps = {
  title?: string;
  children: React.ReactNode;
} & React.HTMLAttributes<HTMLDivElement>;

export function SpreadingExample({
  children,
  title = 'Dr.',
  ...other
}: SpreadingExampleProps) {
  return (
    <div {...other}>
      {title}: {children}
    </div>
  )
}

Handling events

We can type our event handlers to ensure that our event argument is typed properly. The below example demonstrates various ways of achieving this:

export type EventHandlerProps = {
  onClick: (e: React.MouseEvent) => void;
}

export function EventHandler({ onClick }: EventHandlerProps) {
  // handle focus events in a separate function
  function onFocus(e: React.FocusEvent) {
    console.log('Focused!', e.currentTarget)
  }

  return (
    <button
      onClick={onClick}
      onFocus={onFocus}
      onKeyDown={e => {
        // When using an inline function, the appropriate argument signature
        // is provided for us
      }}
    >
      Click me!
    </button>
  )
}

Unsure which argument signature to use? In your editor trying hovering your cursor over the relevant event handler prop.

Using string literals

Do you have a prop which requires a string which matches a set of pre-defined options? You can use the typescript string-literal for this.

type Props = {
  title: "senior" | "junior";
}

function Label({ title }: Props) {
  return <div>Title: {title}</div>
}

Now if the title is anything other than senior or junior, typescript will yell at you.

Using Generics with your react components

This is more of an advanced feature, but one that is really powerful. Typically you’ll define data types in your react components with their specific attributes. Let’s say your component requires a profile object.

type ProfileType = {
  name: string;
  image: string;
  age: number | null;
}

type ProfilesProps = {
  profiles: Array<ProfileType>;
}

function Profiles(props: ProfilesProps) {
  // render a set of profiles
}

But now let’s imagine that you have a component which can accept an array of any type. Generics are analogous to sending a parcel in the mail. The courior (our component) doesn’t need to know the exact contents of the parcel that you’re sending, but the sender (parent component) expects the recipient to get the contents that they sent.

Here’s how we do it:

type GenericsExampleProps<T> = {
  children: (item: T) => React.ReactNode;
  items: Array<T>;
}

export function GenericsExample<T>({
  items,
  children,
}: GenericsExampleProps<T>) {
  return (
    <div>
      {items.map(item => {
        return children(item)
      })}
    </div>
  )
}

A bit of a weird example… but it demonstrates the point. The component accepts an array of items of any type, iterates through that array and calls children as a render function with the item object. When our parent component provides the render callback as a child, the item will be typed properly!

Don’t get it? That’s okay. I still don’t fully understand generics either, but you’re unlikely to need this very often. And the more you work with typescript, the more it’ll make sense.

Typing hooks

Hooks mostly works out of the box. The two exceptions are sometimes useRef and useReducer. The below example demonstrates how we can type refs.

import * as React from 'react'

type HooksExampleProps = {}

export function HooksExample(props: HooksExampleProps) {
  const [count, setCount] = React.useState(0)
  const ref = React.useRef<HTMLDivElement | null>(null)

  // start our timer
  React.useEffect(
    () => {
      const timer = setInterval(() => {
        setCount(count + 1)
      }, 1000)

      return () => clearTimeout(timer)
    },
    [count]
  )

  // measure our element
  React.useEffect(
    () => {
      if (ref.current) {
        console.log(ref.current.getBoundingClientRect())
      }
    },
    [ref]
  )

  return <div ref={ref}>Count: {count}</div>
}

Our state is automatically typed, but we have manually typed our ref to indicate that it will either be null or contain a div element. When we access our ref in our useEffect function, we need to ensure that it’s not null.

Typing a reducer

A reducer is a bit more complex, but having it properly typed is really nice.

// Yeah, I don't understand this either. But it gives us nice typing
// for our reducer actions.
type Action<K, V = void> = V extends void ? { type: K } : { type: K } & V

// our search response type
type Response = {
  id: number;
  title: string;
}

// reducer actions. These are what you'll "dispatch"
export type ActionType =
  | Action<'QUERY', { value: string }>
  | Action<'SEARCH', { value: Array<Response> }>

// the form that our reducer state takes
type StateType = {
  searchResponse: Array<Response>;
  query: string;
}

// our default state
const initialState: StateType = {
  searchResponse: [];
  query: '';
}

// the actual reducer
function reducer(state: StateType, action: ActionType) {
  switch (action.type) {
    case 'QUERY':
      return {
        ...state,
        query: action.value,
      }

    case 'SEARCH':
      return {
        ...state,
        searchResponse: action.value,
      }
  }
}

type ReducerExampleProps = {
  query: string;
}

export function ReducerExample({ query }: ReducerExampleProps) {
  const [state, dispatch] = React.useReducer(reducer, initialState)

  React.useEffect(
    () => {
      if (query) {
        // emulate async query
        setTimeout(() => {
          dispatch({
            type: 'SEARCH',
            value: [{ id: 1, title: 'Hello world' }],
          })
        }, 1000)
      }
    },
    [query]
  )

  return state.searchResponse.map(response => (
    <div key={response.id}>{response.title}</div>
  ))
}

Using typeof and keyof to type component variants

Let’s say we wanted to build a button with various appearances, each defined in an object with a set of keys and styles, like this:

const styles = {
  primary: {
    color: 'blue',
  },
  danger: {
    color: 'red',
  },
}

Our button component should accept a type prop which can be any key of the styles object (i.e, ‘primary’ or ‘danger’). We can type this quite easily:

const styles = {
  primary: {
    color: 'blue',
  },
  danger: {
    color: 'red',
  },
}

// creates a reusable type from the styles object
type StylesType = typeof styles

// ButtonType = any key in styles
export type ButtonType = keyof StylesType

type ButtonProps = {
  type: ButtonType
}

export function Button({ type = 'primary' }: ButtonProps) {
  return <button style={styles[type]}>My styled button</button>
}

These examples should get you 80% of the way there. If you get stuck, it’s often worth looking at existing open source examples.

Sancho UI is a set of react components built with typescript and emotion.

Blueprint is another set of react components built with typescript.

Top comments (13)

Collapse
 
petyosi profile image
Petyo Ivanov

My bread and butter with React and TypeScript is to type the functional components with the React.FC generic type. With your example above, that would be something like this:

export const StandardComponent: React.FC<{ title?: string}> = 
({ children, title = 'Dr.'}) => {
 // rest of the code
}

Why? it adds automatically children for you, makes the signature easier to read (IMHO) and checks the return type.

Note: FC is a short-hand for FunctionComponent

Collapse
 
bmcmahen profile image
Ben McMahen

Cool! I've used FunctionComponent in the past but didn't realize it actually added children for you.

Thanks for the tip!

Collapse
 
bsgreenb profile image
Ben Greenberg 🧢

The React TypeScript cheat sheet recommends doing them using the shorter version of Ben's syntax:

type AppProps = { message: string }; /* could also use interface */
const App = ({ message }: AppProps) => {message};

github.com/typescript-cheatsheets/...

Collapse
 
rixcy profile image
Rick Booth

For someone starting out with React (previous experience with Vue), would you say it's worth integrating and learning TypeScript early on, or would it be better having a decent understanding of React before implementing it?

Collapse
 
cubiclebuddha profile image
Cubicle Buddha • Edited

An alternative opinion: if you already are comfortable enough with TypeScript, then I would recommend starting with TypeScript in React because it’s so helpful to be able to use IntelliSense to automatically see the methods available to each part of React. And it’s also really helpful to know which properties are required on each component. I just started using React and I feel incredibly comfortable and productive in it already due to the types.

Collapse
 
bmcmahen profile image
Ben McMahen

I'd recommend starting with React and JavaScript and moving onto TypeScript later. Learning typescript can add a certain level of friction to the development experience especially if you don't have prior experience with typed languages. Plus, most of the tutorials and guides you'll find will be written in regular Javascript.

Probably best to just focus on learning React - and then move over to typescript once you feel you've got the basics down.

Collapse
 
rixcy profile image
Rick Booth

Cool, thanks for the reply Ben! I'll definitely keep this article bookmarked for when I do start using TypeScript :)

Collapse
 
yazonnile profile image
Slava Z

Hello, Ben
Thx for the knowledge sharing. Im just about to move my internal React project to React+TS, but have faced with problem. Maybe you can help me with this?

Here is a links to code.

gist.github.com/yazonnile/a2268d77...
or
codesandbox.io/s/2ptse

question is - how describe an interface and not get an error? (make method optional will work, but its more like a hack :)

Collapse
 
bmcmahen profile image
Ben McMahen • Edited

Hrmm yeah, using cloneElement with typescript is really messy. I honestly don't have a great solution to this. If I'm forced to use cloneElement I tend to make the cloned values optional, and then throw an error if they aren't actually passed to the child.

Lately I've been preferring to use React.createContext and React.useContext where possible instead, since this gets around the typing issues.

If you do find a better way of typing using cloneElement, let me know!

Collapse
 
js2me profile image
Sergey S. Volkov

Hi, good article, thanks!
Please add format of code snippets
three tick mark plus keyword 'jsx'

Collapse
 
bmcmahen profile image
Ben McMahen

Thanks! I guess they got stripped when I imported the post from my blog. It's updated and looking better.

Collapse
 
georgebaptista profile image
George Baptista

Hey there, fantastic article, really helpful!

One thing - I did have problems getting the reducer example to work.
Do you have a quick sample you can share?

Collapse
 
eacolt profile image
Simon

Its cool! I love React+TS! Its really smart