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}
/>
and creating it might look something like this:
const EditBio: FC = (props): JSX.Element => {
<>
{props.bio}
<br />
{props.open}
</>
};
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,
}
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}
</>
};
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}
</>
};
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,
};
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,
}
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}
</>
};
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,
};
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"
]
},
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}
</>
};
I hope this helped clear up some of the difficulty in passing props in React using TypeScript!
Top comments (1)
Nice introduction to using TS with React! A few points worth mentioning about
React.FC
:JSX.Element
return type declaration is redundant.children
implicitly, so for example if you have a component that doesn't usechildren
prop but that prop is passed in anyways, no errors will be raised. There's a temporary type - VoidFunctionComponen orVFC
, introduced for this reason, which does not takechildren
prop.In general, I find this cheatsheet really helpful for working with TS in React.