Categories
Blog Front End Development How to Javascript React

How to use TypeScript with React: A Brief Overview

I went bravely from using js to jsx files. Then I overcame the gag reflex to write html in javascript. It was time to cover the final threshold with TypeScript and the tsx filetype. In this article, I cover briefly, how to use TypeScript with React.

Defining types within functions

If you’re familiar with TypeScript, then this is how you’re used to declaring your types in functions. This will be important when you also apply this to components:

function convertCurrency(amount: number, currency:string) {}

If the function is returning something, you’ll also need to declare the return type (a common setup for React components):

function myAge(age:number): number {
  return age
}

Using Props the TypeScript way

Props is stored as an object so we also need to define it as such:


function Button(props: { headingText: string }) {
  const { headingText } = props
}

// or

function Button({ headingText }:{ headingText: string }) {}

There are two main methods of declaring props with TypeScript, using interface and type. But they handle things slightly differently. A type can store anything, but an interface is always an object. Here’s an example using type:

type Color = "red" | "green" 

This is known as a restricted type, and will only accept these values (called a union). the string type is inferred so we don’t need to declare it.

Here’s an example using interface. Note it’s being declared as an object ( so the above union declared as an interface would not be possible – it would have to be placed within an object):

interface ButtonProps {
headingText?: string
myColor: Color
myOtherColor?: Color
someArray: boolean[] // array of booleans, unlimited
}

function Button({headingText, myColor, myOtherColor, someArray}: ButtonProps) {}

The use of question mark here is known as optional chaining, and will make the prop an optional value.

The default inferred type of a jsx element in React is React.JSX.Element. You can also import types from other files, like so:

import {type Color} from 'your/types/file'

Using Props for any inline style

You may often find yourself applying inline styles to your components. Rather than defining a bunch of props, you can just use everything that’s available:


type ButtonProps = { style: React.CSSProperties } 

function Button({style}: ButtonProps) {}

Then you can add any inline style you want to your component, as you normally would with vanilla javascript (backgroundColor, borderRadius etc etc).

Using props for any available element attribute

Similar to using styles, you don’t need to declare a prop for every element attribute. Just use the following (where button is the element type):

function Button({...props}: React.ComponentProps<"button">){
return <button {...props}>click</button>
}

This will allow props for any of the available attributes for button. You can also use React.ComponentPropsWithRef and React.ComponentPropsWithoutRef, depending on how you want to use refs reclared on your component.

Note that you can also use things like HTMLElement to declare type, as well as other methods. There are pros and cons, but using the above seems to be the best solution. You might also see other methods used in the wild like React.FC<props>. Whilst this is not necessarily incorrect, it’s not used as much these days.

Read only values

Not to much a React thing, but really a TypeScript thing, it’s useful to know you can set variables to read only:

const textArray = ['one', 'two', 'three'] as const

Handling Events

How you handle events will depend on the context. You don’t need to declare type when events are handled inline (the event is inferred):

function Button(){
return <button onClick={event => console.log('clicked')}></button>
}

But you do if you use a seperate event handler. It’s sometimes tricky to work out what’s required as the type, but intellisense in vs code will give you hints if you’re unsure:

const handleClick = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => console.log('clicked')

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

Declaring component children

Rather than prop drilling, using nested children is a great alternative to pass values between your components. Using React.ReactNode will use any type of child (including plain old text strings). If you replace it with React.JSX.Element, then only JSX elements will be allowed :

type ButtonProps = { children: React.ReactNode }

function Button({children}: ButtonProps) {
return <button>{children}</button>
}

Using state in components

Passing through states in TypeScript can be a bit more tricky. But again, intellisense should be able to help point the way:

type ButtonProps = {
setCount: React.Dispatch<React.SetStateAction<number>>
}

function Button({setCount}: ButtonProps) {}

The useState itself can be declared like so, though this is rarely needed as the initial set value will infer the type. In this example, 0 is a number, so also declaring it as a number is redundant:

const [count, setCount] = useState<number>(0)

Here’s an example where declaring the type is useful. There are two possible values for useState here (User or null) so the type reflects this:

type User = {name: string, age: number}
const [user, setUser] = useState<User | null>(null)
const name = user?.name

Intersect and omit to add to or change existing types

In some cases you might want to add more props after you’ve declared them, after the fact. The ampersand used here will intersect, adding the otherValue prop to the existing type. When applying this with interface, you would use extend:

type ButtonProps = React.ComponentPropsWithoutRef<"button"> & {
otherValue: string
}

// or

type BaseButtonProps = {
color: "red", "green", "blue"
}

type SuperButtonProps = BaseButtonProps & {
colorType: "primary", "secondary"
}

You can also refer to an existing type, using some removed values using omit:

type User = {
sessionID: string,
name: string
}

type Guest = Omit<User, "name">

Setting generics with type

Probably one that’s not use too often, but is useful to know. Rather than leaning on the any type (which is not great for error handling), you can keep your input type the same as your output type like so:

const convertToArray = <T,>(value:T): T[] => {
return [value]
}

The value T in this case could be anything (a number , an array and so on). Writing <T,> sets up this relationship, and using the comma prevents JSX from confusing it as an element. To avoid this, you can also just write the function as follows:

function convertToArray<T>(value: T): T[] {
return [value]
} 

You can also create a relationship between two props, to ensure that they are of the same type:

type ButtonProps<T> = {
countValue: T
countHistory: T[]
}

function Button<T>({countValue, countHistory}: ButtonProps<T>){}

Accept any children AND any native element Prop

The holy grail of React and TypeScript:

type ButtonProps = React.ComponentPropsWithoutRef<"button> & {
  children: React.ReactNode
};

const Button = ({ children, ...props }: ButtonProps) => {
  return (
    <button {...props}>
      {children}
    </button>
  );
};

export default Button

Hint: save this as a snippet when you use typescript with react in your favourite IDE, and all your React dreams will become a reality.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.