TypeScript is a highly useful extension to JavaScript, that helps your code to be sound and robust. In this article, I’ll help you cover typing your React props in TypeScript, as well as some helpful common types to help you out.

Interfaces vs Types for Props

There’s a lot of discourse online for whether or not you should use interfaces for your props or types. Which one you use is up to you; my recommendation is to stick with types, as the flexibility in creating them is very helpful. The one downfall of this is that you can extend interfaces by redefining them, so if you’re for example publishing a project where you expect the types to be extended, you might want to go with interfaces. I’ll stick to types in this article, but in all of the examples in this article, they are interchangeable.

Typing Props

Typing Props in React is simple. The key detail to remember is that your props are passed as an object, so you should type your props as an object.

For example:

function MyImage(src: string,  width: number, height: number) //❎

function MyImage(props: {src: string,  width: number, height: number}) //✅

function MyImage({ src, width, height }: {src: string,  width: number, height: number}) //✅ (Same but with deserialisation)

It’s also very common to extract the type for your props into their own type, rather than doing it inline.

type MyImageProps = {src: string,  width: number, height: number}
function MyImage({ src, width, height }: MyImageProps)

If you prefer lambda functions, you can easily type these as well using a similar approach:

const MyImage = ({ src, width, height }: MyImageProps) => {}

You might have noticed that I’ve omitted any return types from the components in my case. You can just choose to omit these and allow React to infer the return type of your function. If you want to more explicitly check the return type, for example, if you want to check an element is always returned, you can do this:

type AlertProps = {
    message: string;
    level: 'info' | 'warning' | 'critical'; //Type union, very useful
};

function Alert({ message, level }: AlertProps): JSX.Element {
    if (level === 'info')
        return <div className="bg-green-500">Info: {message}</div>;
    if (level === 'warning')
        return <div className="bg-amber-500">Warning: {message}</div>;
    //TS2366: Function lacks ending return statement and return type does not include 'undefined'.
    //^Whoops, we forgot "critical"
}

You can explicitly return “JSX.Element“, or whatever type you need, e.g. “JSX.Element | undefined”

Primitives

Here are a few helpful types I’ve found I’ve used commonly across my React projects.

Firstly, all the primitive/common TypeScript types work as you expect them to:

type AlertProps = {
    message: string;
    duration: number;
    permanent: boolean;
    errors: string[];
}

You can also use optional props:

type AlertProps = {
    ...
    extraInfo?: string;
};

And if you want a default value for your optional prop, you can set this in the prop’s function:

function Alert({ extraInfo = 'Hello World' }: AlertProps) {}

Type Unions are also very useful here, you might have noticed them utilised in the previous example:

type AlertProps = {
    message: string;
    level: 'info' | 'warning' | 'critical'; //Type union, very useful
};

Objects

If you need an object as a prop, the best option is to type your object as best as possible. If you’re looking for a generic object, avoid using something like “{}”.

You’re probably looking for one of these:

type MyObject = {
    myObject: {}; //❎
}

type ObjectProps = { //✅
    anyObject: Record<string, unknown>; //Any object
    anyValue: unknown; //Any value
    emptyObject: Record<string, never>; // {}
};

If you’re using a linter like ESLint, it might already warn you about this.

Passing State

Passing a state value and its corresponding setter function is also a very common pattern you’ll run into:

type MyChildProps = {
    myState: string;
    setState: React.Dispatch<React.SetStateAction<string>>;
};

function MyChild({ myState, setState }: MyChildProps): JSX.Element {}

function MyWrapper() {
    const [myString, setMyString] = useState('');
    return <MyChild myState={myString} setState={setMyString} />;
}

Children

You might run into a common situation where you have a higher-order component that takes a component as a prop. These are really easy to type:

export type WithChildrenProps = {
    children: React.ReactNode;
};

Here’s an example wrapper component:

type BoxProps = {
    title: string;
} & WithChildrenProps;

function Box({ title, children }: BoxProps) {
    return (
        <div className="relative flex flex-col">
            <h1 className="rounded-t bg-primary p-2 text-center">{title}</h1>
            <div className="aspect-square h-48 w-48">{children}</div>
        </div>
    );
}

You can easily add the children prop to your type, use it directly, or create an intersection type to combine them as I have above.

Class Components

Just in case you’re dealing with a legacy codebase, or want to use class components for whatever reason, they can still be used with TypeScript. You can also provide state to a class component the same way:

type AlertProps = {
    message: string;
    level: 'info' | 'warning' | 'critical';
};

type AlertState = {
    active: boolean;
}

class App extends React.Component<AlertProps, AlertState> {
    state: AlertState = {active = true}
    render() {
        if (this.props.level === 'info')
            return <div className="bg-green-500">Info: {this.props.message}</div>;
        //...
    }
}

For Class Components, TypeScript uses generics to give the props and state a type.

Conclusion

Thanks for reading, hopefully, these examples have covered your use case. If you need any extra help, have any suggestions, or if you simply liked the article, feel free to leave a comment below!

Avatar photo
👋 Hey, I'm Omari Thompson-Edwards
Hey, I'm Omari! I'm a full-stack developer from the UK. I'm currently looking for graduate and freelance software engineering roles, so if you liked this article, reach out on Twitter at @marile0n

💬 Leave a comment

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

We will never share your email with anyone else.