TypeScript and React common patterns

A curious Panda

Like any decent tech that will significantly improve your life for the better, TypeScript will slow you down at first. I remember my frustration in the beginning; most of it was related to my little experience with how things should be typed in React.

In this article, I will save you some time (and nerves) by showing you how to type some of the most common React patterns.

React onClick handler

How to correctly type an onClick handler?

Suppose you have a button or a regular link with an onClick handler attached to it.

const handleClick = (event) => {
    event.preventDefault();
    // clicked
}

return (
    <a
        href="#"
        onClick={handleClick}
    >Click me</a>
);

Here, event is of type any. Instead, we can type the event parameter with React.MouseEvent.

const handleClick = (event: React.MouseEvent) => {
    event.gTpreventDefault();
}

Or even better, we can type the handler itself.

const handleClick: React.MouseEventHandler<HTMLAnchorElement> = (event) => {
    event.preventDefault();
};

In this case, the type of event is inferred automatically.

There are more handler types in React, like TouchEventHandler, KeyboardEventHandler, etc. Many of those can be used with multiple DOM events. For example, MouseEventHandler can be used for onMouseUp, onMouseMove, etc.

onSubmit handler

It is similarly easy to type a form handler.


const handleSubmit(e: React.FormEventHandler<HTMLFormElement>) {
    e.preventDefault()
}

return (
    <form onSubmit={handleSubmit}>
        <input type="text" placeholder="Name" />
    </form>
);

Typing a React component

The easiest way to type a React component is to use React.FC.

const MyComponent: React.FC<{ title: string }> = ({ title }) => {
  return <h1>{title}</h1>;
};

The FC type (of course, short for "functional component") takes a parameter - the properties your component accepts.

Sometimes, it looks nicer if you put properties separately.


type Props = {
    title: string
}

const MyComponent: React.FC<Props> = ({ title }) => {
  return <h1>{title}</h1>;
};

Proper typing of useState hook

The useState hook is trivial to type in cases where its type can be inferred from the default value.

const [count, setCount] = useState(0);
// count is inferred to be number

Sometimes, however, the type cannot be inferred. For example, when the default value is null, or an empty array.

In this case, we type the hook explicitly.

const [users, setUsers] = useState<User[]>([]);

React type for useRef

The useRef hook has a wide range of applications. I use it most often to access DOM elements for some direct manipulation.

We want to ensure our ref.current is correctly typed. All we need to do here is to use a type parameter.

const MyComponent = () => {
    const ref = useRef<HTMLDivElement>();

    useEffect(() => {
        if (!ref.current) {
            return;
        }

        // everyone loves aquamarine
        ref.current.style.color = 'aquamarine'
    }, []);

    return (
        <div ref={ref}>
            Hello
        </div>
    );
};

Custom hooks

Custom hooks shouldn't need any special treatment. Just type them as you would type any other function.

Typing a React Context

React context is a great way to pass data down the component tree. Typing it is more complex but still not too complicated.

A common way to use context is to create a context object and then use it to create a Provider and a hook to access the context.

type Value = {
    theme: string
}

const defaultValue: Value = {
    theme: 'default'
}

const ThemeContext = createContext<Value>(defaultValue);

export const ThemeProvider: React.FC = ({ children }) => {
    const [theme, setTheme] = useState(defaultValue.theme);

    return (
        <ThemeContext.Provider value={{ theme }}>
            {children}
        </ThemeContext.Provider>
    );
};

export const useTheme = () => useContext(ThemeContext);

That's all we need to do. Here type Value is the single source of truth. React ensures that whenever we use useTheme hook anywhere in our app, the returning value is correctly typed (is Value).

Note that ThemeProvider can become however complex, but we will always be sure that the value we pass to the context is of the type Value.

What's next

Well, I hope I saved you some time with this quick article.

If you want to dive deeper, here are some links you might enjoy: