How to Manage User Events in React With TypeScript

Juan Diego Rodriguez Feb 02, 2024
How to Manage User Events in React With TypeScript

This tutorial will show how to manage users’ events in React with TypeScript by passing an onClick function from component to component on a user’s actions.

Manage User Events in React With TypeScript

We will be using create-react-app to quickly start and run a new React project.

npx create-react-app my-app --template typescript
cd my-app
npm run start

After installing the necessary packages and starting the development server, we will go to src/App.tsx, delete all the boilerplate code and leave an empty component.

import React from "react";

function Message() {
    return <div></div>;
}

export default Message;

Now we will add a button to the div that the user can click, and we will respond by passing a function with an alert inside the onClick prop.

function Message() {
    return (
        <div>
            <button
                onClick={() => {
                    alert("I was clicked!");
                }}>
                Click Me!
            </button>
        </div>
    );
}

Nothing changes from Vanilla React to TypeScript, but things are different once we want the onClick function to be passed as a prop of the Message component. To show this, we will create another component called Game that will have Message as its child.

function Game() {
    return (
        <div>
            <Message></Message>
        </div>
    );
}

export default Game;

And we will make Message receive its onClick function and text as props coming from Game.

function Message({onClick, text}) {
    return (
        <div>
            <button onClick={onClick}>{text}</button>
        </div>
    );
}

function Game() {
    return (
        <div>
            <Message
                onClick={() => {
                    alert("I was clicked!");
                }}
                text="Click me!"></Message>
        </div>
    );
}

However, we will get the following compilation errors if we run this code.

Binding element 'onClick' implicitly has an 'any' type.
Binding element 'text' implicitly has an 'any' type.

In Vanilla JavaScript, this doesn’t cause an error, but TypeScript throws one since Message’s onClick and text props implicitly have an any type, i.e., we didn’t declare which type those props should store. We must create an interface specifying which type the Message’s props should have to solve this.

interface MessageProps {
    text: string;
    onClick: {};
}

The value that the text prop should have is easy to declare since it’s just a string. But the value for onClick is more difficult.

onClick is more than a regular function since it has an event property, and it is a predetermined property of the button element. So to define onClick, we need a predefined interface that comes with React, which in this case is called ButtonHTMLAttributes and holds all the property types from the button element.

To use it, we have to extend the MessageProps interface to store the ButtonHTMLAttributes types.

interface MessageProps extends ButtonHTMLAttributes {
    text: string;
}

However, this isn’t enough, and running the code like this will throw an error since the ButtonHTMLAttributes interface is a Generic Type. You can think of Generic Types as interfaces with variables, and to use them, we wrap them around <> after declaring the interface.

In this case, the ButtonHTMLAttributes interface needs a variable to know which HTML element we are using, and it will be the global HTMLButtonElement.

interface MessageProps extends ButtonHTMLAttributes<HTMLButtonElement> {
    text: string;
}

MessageProps doesn’t only hold the types for the text and onClick props but the types for all props of a button element. You could add any prop from a button to Message.

In case you want to only extend the onClick prop, you don’t extend the interface, create a new onClick type and assign the ButtonHTMLAttributes’s onClick property using Indexed Access Types.

interface MessageProps {
    text: string;
    onClick: ButtonHTMLAttributes<HTMLButtonElement>["onClick"];
}

To finish, we must declare that the Message component will use MessageProps for its props in the following way.

function Message({onClick, text}: MessageProps) {
    return (
        <div>
            <button onClick={onClick}>{text}</button>
        </div>
    );
}

And if we want, we can annotate the return type to be a JSX.Element so TypeScript will throw an error if we accidentally return some other type.

function Message({onClick, text}: MessageProps): JSX.Element {
    return (
        <div>
            <button onClick={onClick}>{text}</button>
        </div>
    );
}

And this would be the final result.

import React from "react";
import {ButtonHTMLAttributes} from "react";

interface MessageProps {
    text: string;
    onClick: ButtonHTMLAttributes<HTMLButtonElement>["onClick"];
}

function Message({onClick, text}: MessageProps): JSX.Element {
    return (
        <div>
            <button onClick={onClick}>{text}</button>
        </div>
    );
}

function Game() {
    return (
        <div>
            <Message
                onClick={() => {
                    alert("I was clicked!");
                }}
                text="Click me!"></Message>
        </div>
    );
}

export default Game;

Code example

Juan Diego Rodriguez avatar Juan Diego Rodriguez avatar

Juan Diego Rodríguez (also known as Monknow) is a front-end developer from Venezuela who loves to stay updated with the latest web development trends, making beautiful websites with modern technologies. But also enjoys old-school development and likes building layouts with vanilla HTML and CSS to relax.

LinkedIn