DEV Community

loading...
Cover image for Typing React Props in TypeScript
TypeScript TV

Typing React Props in TypeScript

Benny Neugebauer
Writing about JavaScript / TypeScript and productivity hacks! ⚡️ Recording videos for "TypeScript TV" on YouTube. 🎬
・3 min read

One advantage of using React with TypeScript is that you can easily type the props of your (function) components. You don't have to use React's PropTypes because TypeScript already has its own typing system.

In the following, I will show you how to define custom props for a component in connection with already existing props like children.

Starting Example

PostPreview.tsx

import React from 'react';

export interface Props {
  heading: string;
}

const PostPreview = (props: Props) => {
  return (
    <div>
      <h1>{props.heading}</h1>
      {props.children}
    </div>
  );
};

export default PostPreview;
Enter fullscreen mode Exit fullscreen mode

As you can see, our PostPreview component has a heading property. The component is supposed to render the heading and other components (children) below the heading. In technical terms this is called Containment.

Because our Props interface only defines the heading, the following error shows up:

TS2339: Property 'children' does not exist on type 'Props'.

Let me show you three different ways to solve this problem.

Solution 1: PropsWithChildren

The easiest way to solve the problem is to use the generic type PropsWithChildren. It supports a generic type variable, so that we can use our Props with it:

import React, {PropsWithChildren} from 'react';

export interface Props {
  heading: string;
}

const PostPreview = (props: PropsWithChildren<Props>) => {
  return (
    <div>
      <h1>{props.heading}</h1>
      {props.children}
    </div>
  );
};

export default PostPreview;
Enter fullscreen mode Exit fullscreen mode

The solution is simple, but it doesn't describe our component very well. The compiler knows that our component can have children, but it doesn't know whether our component has other tag-specific properties. We also have to remind ourselves to import React. So let's take a look at a more advanced solution.

Solution 2: React.FC

React.FC specifies a function component and lets us also assign a type variable. It uses PropsWithChildren behind the scenes, so we don't have to worry about connecting our Props with it:

import React from 'react';

export interface Props {
  heading: string;
}

const PostPreview: React.FC<Props> = (props) => {
  return (
    <div>
      <h1>{props.heading}</h1>
      {props.children}
    </div>
  );
};

export default PostPreview;
Enter fullscreen mode Exit fullscreen mode

Thanks to the use of React.FC, the TypeScript compiler now knows that our PostPreview constant is a React component. We no longer have to think about importing React ourselves, as the compiler already prompts us to do so. However, the compiler still does not know how our component looks like in detail. It cannot tell whether it is a <div> element or a <p> element or something else. Hence we come to solution number three.

Solution 3: React.HTMLProps

The most specialized version is to extend React.HTMLProps. The HTMLProps support a variety of tags (HTMLDivElement, HTMLFormElement, HTMLInputElement, etc.). Make sure that the type variable matches the outmost tag (the first tag, that is mentioned after return):

import React from 'react';

export interface Props extends React.HTMLProps<HTMLDivElement> {
  heading: string;
}

const PostPreview = (props: Props) => {
  return (
    <div>
      <h1>{props.heading}</h1>
      {props.children}
    </div>
  );
};

export default PostPreview;
Enter fullscreen mode Exit fullscreen mode

With this variant our component inherits all properties of a <div> element and extends them with custom props like heading.

Our PostPreview component can now be used as follows:

IndexPage.tsx

import React from 'react';
import PostPreview from './PostPreview';

const IndexPage: React.FC = () => {
  return (
    <div>
      <PostPreview heading="First Post">
        <p>#1</p>
      </PostPreview>

      <PostPreview heading="Second Post">
        <p>#2</p>
      </PostPreview>
    </div>
  );
};

export default IndexPage;
Enter fullscreen mode Exit fullscreen mode

Tested with: React v17.0.2

Want more? 🍨

Please subscribe to TypeScript TV on YouTube if you liked this post.

In addition you can follow me on DEV to learn about best practices with TypeScript & JavaScript.

Discussion (6)

Collapse
lukeshiru profile image
LUKESHIRU

One thing you can do instead of using HTMLProps and having to know the actual type of every HTML tag, is to use JSX.IntrinsicElements this way:

JSX.IntrinsicElements["div"]; // All properties of a "div"
JSX.IntrinsicElements["span"]; // All properties of a "span"
JSX.IntrinsicElements["input"]; // You guested it! All properties of an "input"
Enter fullscreen mode Exit fullscreen mode

So your example could be something like this:

import { FC } from "react";

export type PostPreviewProps =
    JSX.IntrinsicElements["div"] & {
        // I used "heading" here because
        // "title" is already a prop of "div"
        readonly heading?: string;
    };

export const PostPreview: FC<PostPreviewProps> = ({
    children,
    heading,
    ...props
}) => (
    <div {...props}>
        <h1>{heading}</h1>
        {children}
    </div>
);
Enter fullscreen mode Exit fullscreen mode

Cheers!

Collapse
bennycode profile image
Benny Neugebauer Author

Thank you for sharing this solution with us! 🙂 Since the "title" attribute really is a global HTML element attribute, I will also update my tutorial to use "heading" instead of "title". 👍

Collapse
myfatemi04 profile image
Michael Fatemi

If you want you can also just add children as an optional field with the type React.ReactNode

Collapse
bennycode profile image
Benny Neugebauer Author

Good idea! That's a self-made PropsWithChildren solution. :)

Collapse
sametweb profile image
Samet Mutevelli

Thanks for this article. I always use React.FC for children and key props.

Collapse
bennycode profile image
Benny Neugebauer Author

I am glad to hear that you could find something interesting about this article. :) Are there any other topics related to React & TypeScript that you would like to read more about?