TypeScript: Function Declarations vs. Function Expressions

Joe Previte
Joe PreviteFriday, December 13, 2019
A woman sitting at a desk looking at a iMac.

When I first started learning TypeScript and using it with React, I followed patterns. A friend showed me how to type a component. I used that as an example whenever I created a new component. This example looked something like this:

const MyComponent: React.FC = () => <h1>Hello, world!</h1>

Experimenting with this pattern, I thought to myself, “If I want to declare a component using the function keyword, then I bet I can do the same thing.”

function MyComponent(): React.FC { return <h1>Hello, world!</h1> }

Sadly, this is not valid. Instead of investigating deeper, I decided to stick with what worked (using function expressions like the first example). Eight months later, after diving deeper into TypeScript, I’ve finally learned the difference and understand why my first attempt didn’t work.

Typing a React Component as a Function Expression

When we type a React component as a function expression (i.e. const MyComponent: React.FC), we have to ask ourselves, “what are we annotating and what type of value does this variable MyComponent hold?”

We’re annotating the function type because this variable holds a function. React.FC indicates a “React Function Component”. So that’s why we type it this way.

Typing a React Component as a Function Declaration

In the other instance, when we type a React component as a function declaration (i.e. function MyComponent), we ask the same thing.

This time, we’re annotating the function return type. This explains why we can’t use the same type! We instead need to tell TypeScript, “Hey! This function here is going to return a React component.” So what does that look like? Like this:

function MyComponent(): React.ReactNode { return <h1>Hello, world!</h1> }

Now, TypeScript knows that this function will return some type of ReactNode. If you haven’t seen ReactNode before, you’ll notice from the @types/react declaration file that it is a union type that looks like this:

type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;

These are all the valid things that can be returned by a component. Feel free to take a look at the source code here.

And now you know how to properly type a React component as both function expressions and function declarations! Check out the react-typescript-cheatsheet to learn more.

Typing Props with a Function Expression

When your components accept props, it’s important to know where those get added in as well. Here’s an example:

type Props = { name: string; } const MyComponent: React.FC<Props> = ({ name}) => <h1>Hello, {name}!</h1>

In addition, we can use a handy type called PropsWithChildren which will automatically include the children prop for us:

type Props = { name: string; } const MyComponent: React.FC<PropsWithChildren<Props>> = ({ name, children }) => <h1>{children}, {name}!</h1>

Typing Props with a Function Declaration

We can do the same thing with a function declaration:

type Props = { name: string; } function MyComponent({ name }: Props): React.ReactNode { return <h1>Hello, {name}!</h1> }

And the same for using PropsWithChildren :

type Props = { name: string; } function MyComponent({ name }: PropsWithChildren<Props>): React.ReactNode { return <h1>{children}, {name}!</h1> }

Pretty neat, huh?

Credit to Chau Tran for teaching me about this on Twitter!

LAST UPDATED: December 17, 2019

Share this post


Related Posts:

Interested in working with us?

Give us some details about your project, and our team will be in touch with how we can help.

Get in Touch