Chidume Nnamdi I'm a software engineer with 6+ years of experience. I've worked with different stacks ranging from WAMP, to MERN, to MEAN. My language of choice is JavaScript; frameworks are Angular and Nodejs.

Your reference guide to using TypeScript in React

6 min read 1856

React Typescript Guide

One of the issues with JavaScript is its dynamically-typed nature, meaning that data and variable types are unknown until runtime. This can have many side effects. For example, it can cause confusion within your codebase due to the fact that a variable can be anything.

To solve this issue, Microsoft released TypeScript. Anders Hejlsberg, lead architect of TypeScript, said, “What if we could strengthen JavaScript with the things that are missing for large scale application development, like static typing, classes [and] modules…? That’s what TypeScript is about.”

TypeScript instantly became the most widely used static-typed version of JavaScript. It enabled JavaScript devs to statically type data and variables. Soon enough, it was introduced to React.js, enabling React devs to write their React app in TypeScript.

But this comes at a cost: TypeScript typings and syntaxes can be difficult to keep up with, especially when used with React.

React has many features, such as props, class components, function components, function params, components lifecycle hooks, and member properties. Because typing these within TypeScript isn’t easy, this article aims to serve as a quick reference and learning guide for both beginner and advanced React devs.

With it, you’ll be able to quickly look up best practices and generic TS types in React. Ready? Let’s get started.

TypeScript typings

TypeScript has a typings folder where it saves files with *.d.ts extension. These files include interfaces that infer what shape a value will take. This is what enables TypeScript to bring data-typing to JavaScript.

An interface describes what a value would look like:

type AppState {
    propOne: number;
    propTwo: string
}

AppState describes what the value of its data-type would look like. First, we infer it would be an object that holds properties propOne and propTwo, the former being of number type and the latter being a string type. Assigning a boolean type to propOne would cause TypeScript to throw TypeError.

When we include TypeScript in our React project, every React element has an interface that defines the shape it will take. Let’s start with function component.

We made a custom demo for .
No really. Click here to check it out.

Function component

Function components are normal functions that return JSX elements in React and are used to create views. Initially, they are stateless components, but with the arrival of React hooks, they can be made stateful and smart/

Defining a React function component takes the React.FunctionComponent shape:

function App: React.FunctionComponent<> {
    return (
        <>
            // ...
        </>
    )
}

We can also use the shorthand React.FC:

function App: React.FC<> {
    return (
        <>
            // ...
        </>
    )
}

React.FunctionComponent, or React.FC, describes explicitly the return type of the function component.

We can type the props definitions in the arrows <>.

type AppProps = {
    message: string;
    age: number;
};

AppProps is the interface the props passed to App will take, so we can write the App component below if it would receive props:

type AppProps {
    message: string;
    age: number;
}

function App: React.FC<AppProps>(props: AppProps) {
    return (
        <>
            // ...
        </>
    )
}

We can use ? to set optional values in the typing:

type AppProps {
    message: string;
    age?: number;
}

Now, the age property becomes optional. The App component can be rendered, omitting the age property in its props object.

We can omit the type declaration within the arrows <>.

function App<{message: string; age: number;}>({message: string; age: number;}: AppProps) {
    // ...
}

Inner functions in the functional component can be typed, like so:

function App<{message: string; age: number;}>({message: string; age: number;}: AppProps) {
    // ...

    function clickHandler (val: number) {
        // ...
    }

    return (
        <>
            <button onClick={() => clickHandler(45)}            
        </>
    )
}

Class component

Class components are used to create views in React. They are essentially building blocks. A React app can contain many of them, and they define how a small unit of the UI should appear.

Class components have lifecycle hooks that we can hook into to run custom code at any state of the component’s lifecycle.

They use the React.Component<>:

class App extends React.Component<> {
    // ...    
}

Props and State

You can provide class components with props and state type parameters:

type AppProps = {
    message: string;
    age: number;
};

type AppState = {
    id: number;
};

class App extends React.Component<AppProps, AppState> {
    state: AppState = {
        id: 0
    };

    // ...    
}

The props type AppProps is inserted between the <> arrows before the state type AppState.

Class methods

If you have arguments for class methods, the arguments can be typed:

class App extends React.Component<AppProps, AppState> {
    state: AppState = {
        id: 0
    };

    clickHandler = (val: number) => {
        // ...
    }

    render() {
        return (
            <>
                <button onClick={() => this.clickHandler(90)}>Click</button>
            </>
        )
    }
}

Function components and defaultProps typing

defaultProps is used to define default values for the props arguments passed to React components. defaultProps is helpful for preventing errors when a supposedly available prop becomes missing during runtime. The values within defaultValues immediately become the value of the props.

React.Fc is dropped when including defaultProps in function components because some edge cases in type inference are still a problem in TypeScript.

This means that props are used like they are normally:

type AppProps = { message: string; age: number } & typeof defaultProps;
const defaultProps = {
    message: "",
    age: 0
};

function App(props: AppProps) {
    // ...
};

App.defaultProps = defaultProps;

Forms and events

Forms are generally used to collect information in an internal state. They are mostly used in login and registration pages so that information submitted can be collected by the form and sent to the server for processing.

React.FormEvent is used to generally type events from elements.

class App extends React.Component<> {
    clickHandler = (e: React.FormEvent<HTMLButtonElement>) => {
        // ...
    }

    changeHandler = (e: React.FormEvent<HTMLInputElement>) => {
        // ...
    }

    render() {
        return (
            <div>
                <button onClick={this.clickHandler}>Click</button>
                <input type="text" onChange={this.changeHandler} />
            </div>
        )
    }
}

Both clickHandler and changeHandler arguments e has the React.FormEvent type.

We can omit typing the handler’s argument with React.FormEvent and type the return value of the handlers instead. This is done using the React.ChangeEventHandler:

class App extends React.Component<> {
    clickHandler: React.FormEvent<HTMLButtonElement> = (e) => {
        // ...
    }

    changeHandler: React.FormEvent<HTMLInputElement> = (e) => {
        // ...
    }

    render() {
        return (
            <div>
                <button onClick={this.clickHandler}>Click</button>
                <input type="text" onChange={this.changeHandler} />
            </div>
        )
    }
}

React.ChangeEvent<T> is an event type that can be used to type the event handler argument:

class App extends React.Component<> {
    clickHandler = (e: React.ChangeEvent<HTMLButtonElement>) => {
        // ...
    }

    changeHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
        // ...
    }

    render() {
        return (
            <div>
                <button onClick={this.clickHandler}>Click</button>
                <input type="text" onChange={this.changeHandler} />
            </div>
        )
    }
}

Note that T is the type of the element that the event is registered on.

Button

This describes the React.ChangeEvent type for the button element. Buttons are instances of the HTMLButtonElement class. Here, T will be HTMLButtonElement, and React.ChangeEvent will be React.ChangeEvent.

Inputs

This describes the React.ChangeEvent for all input elements: “text,” “password,” “color,” “button,” “media,” etc. They are all instances of the HTMLInputElement class. For all types=[“text”, “password”, etc]

T will be HTMLTnputElement. React.ChangeEvent will be React.ChangeEvent.

TextArea

This refers to the React.ChangeEvent for the text area element, and text area elements are instances of the HTMLTextAreaElement.

T will be HTMLTextAreaElement.
React.ChangeEvent will be React.ChangeEvent.

Select

Select element is used to create a dropdown list with options for selection. Select elements are instances of HTMLSelectElement.

T will be HTMLSelectElement. React.ChangeEvent will be React.ChangeEvent.

Form

Form elements collects information from the DOM. Form elements are instances of the HTMLFormElement. T will be HTMLFormElement. React.ChangeEvent will be React.ChangeEvent.

Video, Audio

Video is used to play videos on the browser. The video element is an instance of the HTMLVideoElement. This describes how to set the React.ChangeEvent in the Form element.

T will be HTMLVideoElement.
React.ChangeEvent will be React.ChangeEvent.

In turn, Audio is used to play audio files (mp3, aac, etc.) in the browser. The audio element is an instance if the HTMLAudioElement.

T will be HTMLAudioElement.
React.ChangeEvent will be React.ChangeEvent.

React.SyntheticEvent

This type is used when you aren’t concerned with the type of event:

class App extends React.Component<> {
    submitHandler = (e: React.SyntheticEvent) => {
        // ...
    }

    render() {
        return (
            <form onSubmit={this.submitHandler}>
                ...
            </form>
        )
    }
}

Hooks

Hooks are supported in Reactv16.8+. They allow us to use state in functional components.

useState

This is used to set state in functional components, and the state is kept alive throughout the lifetime of the component.

const [state, setState] = useState(0)

We can infer state to be a number and setState a function that takes numbers.

const [state, setState] = useState<number, (v: boolean) => : void >(0)

If the state takes an object:

type StateObject = {
    loading: boolean
}

const [state, setState] = useState<StateObject, (boolean) => : void >({loading: true})

Often times, the useState initial value is null. We can use the union type to explicitly declare type, like so:

const [state, setState] = useState<StateObject | null, (boolean) => : void >(null)

This StateObject | null tells TypeScript that the state can be of StateObject type or of null type.

useReducer

useReducer allows us to maintain state and concurrently dispatch actions to the store from our functional components. It takes an initial state and a reducer function:

const intialState = {
    loggedIn: false
}

function reducer(state, action) {
    // ...    
}

const [state, dispatch ] = useReducer(initialState, reducer)

We can define a type of the state, then type the reducer function based on the return type. Dispatch can be typed based on the action argument type on the reducer function:

type LogginState = {
    loggedIn: boolean;
};

type ActionType = {
    type: string;
    payload: boolean;
};

const intialState: LogginState = {
    loggedIn: false
}

function reducer(state: LogginState, action: ActionType): LogginState {
    // ...    
}

const [state, dispatch ] = useReducer<LogginState, (v: ActionType) => : void>(initialState, reducer)

useRef

useRef allows us to access refs in React nodes and maintain them throughout the lifetime of the component.

null types are mostly passed to useRef, so the typing would look like this:

const ref= useRef<HTMLElement | null>(null)

We can be more specific in the type of element we are trying to reference:

// this types useRef to button elements
const buttonRef= useRef<HTMLButtonElement | null>(null)

// this types useRef to input elements
const inputRef= useRef<HTMLInputElement | null>(null)

Also, useRef can be used on React.Component:

const inputRef= useRef<React.Component | null>(null)

useContext

useContext allows us to create and maintain contexts in our functional components throughout their lifetime. The contexts are created using the React Context API.

Here’s how to use useContext and createContext to create a context:

const authContext = createContext({isAuth: true})
const context = useContext(authContext)

The default value type of createContext will form the basis of its argument type:

type AuthType = {
    isAuth: boolean;
};

const authContext = createContext<AuthType>({isAuth: true})

Also, useContext argument type will be AuthType:

const context = useContext<AuthType>(authContext)

Conclusion

Hopefully you’ve found this guide informative and helpful. You can also read more about React TypeScript type inference in this Github repo.

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — .

Chidume Nnamdi I'm a software engineer with 6+ years of experience. I've worked with different stacks ranging from WAMP, to MERN, to MEAN. My language of choice is JavaScript; frameworks are Angular and Nodejs.

Leave a Reply