DEV Community

loading...
Cover image for Passing props in React using Typescript

Passing props in React using Typescript

mconner89 profile image Mike Conner ・3 min read

I've recently been working on a project and attempting to teach myself a bit of TypeScript as I go. It's been.... interesting, to say the least. As someone who only knows JavaScript, the idea of strictly-coded types seemed incredibly clunky and foreign to me. Something that I thought I had a very good grasp on, like passing props down in React, suddenly became incredibly difficult! So in this post, I just want to talk a bit about how I've been approaching this process.

Grabbing directly from props

Lets assume you're trying to pass a series of props down to a lower component in react, you're not going to do any kind of destructuring, and you're not passing any objects. So calling the lower-level component might look something like this:

<EditBio
  bio={bio}
  open={open}
/>
Enter fullscreen mode Exit fullscreen mode

and creating it might look something like this:

const EditBio: FC = (props): JSX.Element => {
  <>
    {props.bio}
    <br />
    {props.open}
  </>
};
Enter fullscreen mode Exit fullscreen mode

To make TypeScript happy, we need to tell it what to expect on that props object. To accomplish this, we need to create an interface. The interface will contain props that we're going to be referencing and their types. For our example, we might create something like this:

interface BioProps {
  bio: string,
  open: boolean,
}
Enter fullscreen mode Exit fullscreen mode

And after that, we'll need to edit our component creation to include the interface like so:

const EditBio: FC<BioProps> = (props): JSX.Element => {
  <>
    {props.bio}
    <br />
    {props.open}
  </>
};
Enter fullscreen mode Exit fullscreen mode

But what if our prop is an object itself? Lets take a look at that!

Dealing with objects

So in this example, we're going to destructure something out of our props, and that destructured item will also be an object. So our initial component creation might look something like this:

const UserProfile: FC = ({ user }): JSX.Element => {
  <>
    {user.firstName}
    <br />
    {user.dob}
    <br />
    {user.bio}
  </>
};
Enter fullscreen mode Exit fullscreen mode

So we'll need to do two things here. First, we'll need to create a new type that defines the types of each property of the user object. That might look something like this:

 type UserType = {
    dob: string,
    firstName: string,
    userBio: string,
  };
Enter fullscreen mode Exit fullscreen mode

After that, we'll need to define an interface just like we did previously, but we're going to want to use this type we just created to denote that we're expecting an object that matches this type. This is relatively simple:

interface UserProps {
  user: UserType,
}
Enter fullscreen mode Exit fullscreen mode

And finally, we just pop that shiny, new interface in our component creation function and TypeScript should be happy!

const UserProfile: FC<UserProps > = ({ user }): JSX.Element => {
  <>
    {user.firstName}
    <br />
    {user.dob}
    <br />
    {user.bio}
  </>
};
Enter fullscreen mode Exit fullscreen mode

Passing the same prop down to multiple components

There's one more scenario I'd like to talk about, and that is what to do when you need to pass down that same user object to multiple components. You could just declare the type and interface in every component. Or you could declare it once and export that declaration everywhere you need it. So lets take a look at that! First, you're going to want to make a folder in your src folder called "customTypings". If this is a large project and you have many different types/interfaces to declare, you might want to make sub-folders in this folder for each module you want to export, so we'll make one as an example and name it after the module you'll be exporting (myTypes in this example). Finally, we'll make a file called index.d.ts, and this is where all of our custom types will live. So in that file, lets declare the UserType type and UserProps interface we used in our previous example and export them:

declare module 'myTypes' {
  type UserType = {
    dob: string,
    firstName: string,
    userBio: string,
  };

  interface UserProps {
    user: UserType,
  }
}

module.exports = {
  UserType,
  UserProps,
};
Enter fullscreen mode Exit fullscreen mode

After this, we'll need to head into our tsconfig.json, into the compiler options, and make a new option called typeRoots (or edit the existing one). That should look something like this:

 "compilerOptions": {
    "typeRoots": [
      "src/customTypings",
      "node_modules/@types"
    ]
  },
Enter fullscreen mode Exit fullscreen mode

Now everywhere you want to use either our type or our interface, you just have to export it at the beginning of your component like you would anything else:

import { UserProps } from 'myTypes';

const UserProfile: FC<UserProps > = ({ user }): JSX.Element => {
  <>
    {user.firstName}
    <br />
    {user.dob}
    <br />
    {user.bio}
  </>
};
Enter fullscreen mode Exit fullscreen mode

I hope this helped clear up some of the difficulty in passing props in React using TypeScript!

Discussion (1)

pic
Editor guide
Collapse
clarity89 profile image
Alex K.

Nice introduction to using TS with React! A few points worth mentioning about React.FC:

  • It also handles the return type from the component, so JSX.Element return type declaration is redundant.
  • It types children implicitly, so for example if you have a component that doesn't use children prop but that prop is passed in anyways, no errors will be raised. There's a temporary type - VoidFunctionComponen or VFC, introduced for this reason, which does not take children prop.

In general, I find this cheatsheet really helpful for working with TS in React.