Should You Use the FC Type in React?

React gives us different ways to add specific types to different parts of our components. We can do this in several ways. Today I want to focus on static and dynamic typing, and on the FC type we can meet in React.

Children

From this article you will learn:

  • What are the differences between statically and dynamically typed languages?
  • What changes when TypeScript enters a JavaScript project?
  • What are the ways to type things in React?
  • What does the ReactNode type contain?
  • Should we use FC, and when does it make sense?

A component is the smallest unit from which an application written in React is built. It is worth emphasizing that a component, in the sense used by this library, should be reusable in such a way that each of its uses remains independent from the others.

In other words, a component is nothing more than a JavaScript function to which we pass arguments when calling it. The first argument, which you certainly know, is an object commonly called props.

React gives us two ways to create components - the first is a function component, the second is a class component. From React's point of view, both components are identical. Also, no reliably performed performance tests have shown that either approach is more optimal than the other. Because of that, it is up to us how we want to write. It is worth adding, however, that in recent years the creators of React have strongly promoted writing components in a functional way - blurring the differences between both approaches, for example by introducing hooks.

Today I want to talk not so much about components themselves, but about typing them - but let us start from the beginning.

Typing

The whole magic of JavaScript lies in its flexibility, which shows up, for example, in the fact that it is a dynamically typed language. What do we mean by that? First of all, dynamically typed languages allow us to freely change the type of data stored in a variable. In practice, this means that types are not checked before the program runs. Data types are therefore assigned to values stored in variables while the program is running. Below is an example of dynamic typing in PHP:

php
<?php 

$age = 32;

getType($age); // integer

if ($age > 18) {
  $age = "adult";
}

getType($age) // string

As you can see, freely changing the type did not cause any error here. It would work similarly in JavaScript.

js
let age = 32;
typeof age; // number

if (age > 18) {
	age = 'adult';
}

typeof age; // string

This approach stands in opposition to static typing, which we meet in languages such as Java or C#. A type has to be assigned to a variable already when it is created and cannot be changed at later stages of code execution. An attempt to make such a change ends with a compilation error.

java
class Age {
	public static void main() {
		int age = 32;

		if (age > 18) {
			age = "adult"; // error
		}
	}
}

Now that we can see the difference between dynamically and statically typed languages, we should ask ourselves what is more useful from the perspective of writing code. I have worked as a programmer since 2011, and for most of that time I have worked in dynamically typed languages such as JS and PHP. I have always appreciated them for the freedom they give and for the ability to build software relatively quickly. Those are definitely their advantages. But what about the disadvantages?

Cannot read property X of undefined

In JavaScript, we see this error often when trying to refer to a value that is undefined. In other words, it means that a variable has been declared but has no value. It appears whenever we try to access a property or function on a variable that is not an object. And it may not be one. After all, this is only a dynamically typed language.

This error is everyday life for a frontend developer. Ask a Java programmer whether they have ever encountered a similar problem in their work.

Game Changer

Frontend programmers increasingly appreciate the advantages of statically typed languages. More and more projects are written in TypeScript, which is a superset of JavaScript. TypeScript adds static typing to JS, but only during development.

Great news, right? Well, actually, it is only half true, because in the end the browser still runs code transpiled to JavaScript. So we can use the advantages of static typing while building the application, which has still become something of a game changer in the frontend world.

propTypes

Let us return to React. Since React is written in JavaScript, it does not force any type checking on the data we work with. However, the larger an application written in React becomes, the more useful type checking may be. That is how the separate prop-types library appeared (previously it was part of React itself), giving us the ability to add typing to our component props.

How does it work?

js
import propTypes from 'prop-types';

const Component = ({ name }) => {
	return null;
}

Component.propTypes = {
	name: propTypes.string,
	data: propTypes.shape({
    age: propTypes.number,
    favorite: propTypes.oneOf(['book', 'coffee', 'programming']),
  }),
};

In the example above, we defined that our props will contain two keys: name (which will be a string) and data (which will be an object with two more properties: age (number) and favorite (one of three things)). That is already something. However, for performance reasons, propTypes are checked only in development mode. If we pass a value of a different type than expected, it will display a warning in the console, and in production it will result in an error.

TypeScript

We can also use TypeScript in React projects. It lets us type not only props, but also whole components and other parts of our code. React itself provides quite a few different types that we can use in our project.

tsx
import React, { FunctionComponent, SyntheticEvent } from 'react';

interface ComponentProps {
	label: string;
	action: () => void;
}

const Component: FunctionComponent<ComponentProps> = ({ label, action }) => {
	const handleClick = (e: SyntheticEvent) => {
		action();
	}

	return <button onClick={handleClick}>{label}</button>
}

As we can see, we have many more possibilities than with prop-types. We typed not only the props, but also the whole component and the arguments of internal functions. It looks like TS gives us much more. I would like to focus, however, on one type I used above: FunctionComponent, or FC for short.

Why Type Components?

It turns out that a function component can be typed in different ways. But why type it at all? First of all, typing a component lets us describe props and define the type of the returned value. This increases the readability of our code and definitely makes it easier to control what we write. React gives us a type that we can use to describe our component, namely the already mentioned FunctionComponent. So we can type a component using FC, or we can avoid it.

tsx
const A: FC<{ loading: boolean }> = ({ loading }) => <div>{loading && 'Trwa ładowanie'}</div>;

const B = ({ loading }: { loading: boolean }) => <div>{loading && 'Trwa ładowanie'}</div>;

If we can do this with TypeScript alone, why does the FC type exist at all? The problem needs to be found in component children.

Problems with Children

A React component can accept one very specific prop, namely children. Children are simply elements that have been passed to a component as nested elements (children in the structure). Inside the component, we can render those elements in any place we choose, using the children property. FC is a generic type that has the children property described appropriately. If we needed children and typed the component ourselves, we would have to remember to declare it in the type. It would look like this:

tsx
// only ts, without the FC type

const C = ({ children }: { children: React.ReactNode }) => <p>{children}</p>

// with the FC type

const D: FC => <p>{children}</p>

It may therefore seem that using the FC type is justified and reasonable. It helps us with typing props, gives us a correct definition of the children prop, and generally shortens the notation.

React.ReactNode

React.ReactNode is the broadest type we can use to describe what a function component may return. Notice that it can return many more values than only a React element or text.

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

Until React 18, returning undefined from a component was practically impossible. React returned an error:

Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.

In practice, this meant that we had to use null if we wanted to return nothing.

So What Is the Problem?

Returning to children, FC always adds information about children to the props type definition, even when it was not needed or when we did not want that definition there. If we add information about children ourselves, TS will tell us whether our component should have children or not.

tsx
const ComponentA = ({ name }: { name: string }) => {
	return <p>{name}</p>;
}

const ComponentB = () => {
	// Type '{ children: Element; name: string; }' is not assignable to type 'IntrinsicAttributes & { name: string; }'.
	// Property 'children' does not exist on type 'IntrinsicAttributes & { name: string; }'.ts
	return <ComponentA name="Kamila"><div /></ComponentA>
}

As we can see in the example above, the TS message in this case is quite clear: the ComponentA component should not have children. If we use the FC type, there will be no type error.

Another fairly important issue is the use of the defaultProps property to describe default values for props. defaultProps are a leftover from the times of class components, although we can still meet situations where using them will be necessary. Unfortunately, when combining React.FC and defaultProps, we get an error saying that we did not pass the required props to our component. TypeScript, however, has supported defaultProps since version 3.1.

Updated on 19 July 2023

The problem above has been partially solved. Since React 18, children has been removed from the default declaration of the FC type. At the same time, a new generic type, PropsWithChildren, was added. It includes the children property as optional. Below you can see the declaration of the new type.

ts
type PropsWithChildren<P> = P & { children?: ReactNode | undefined };

This is partially in line with our expectations. Unfortunately, because children are marked as optional, TypeScript still will not tell us that we forgot to pass children when we actually did forget.

tsx
const Comp: FC<PropsWithChildren<{ name: string }>> = ({ name, children }) => (
  <div>
    <h2>{name}</h2>
    {children}
  </div>
);

Additionally, because several generic types are nested inside one another, the code becomes less readable and more complex. Of course, this is only my feeling, but it seems to me that people who receive a component typed this way may feel slightly lost.

Summary

To use it or not to use it? We can come across the opinion that for people beginning with TS and React it will be much easier to use React.FC, because it gives relative control over what is happening in our components. However, more advanced programmers will definitely move toward having greater control over their components.

Finally, it is worth adding that the FC type replaced the SFC type (Stateless Function Component), which is now marked as deprecated. In React 16.9, VFC (Void Function Component) was introduced to describe components that would not have children. In React 18, however, it was also marked as deprecated. Because of all this confusion, it is worth asking whether it is not better to move to typing functions in React ourselves. There is a lot going on around the FC type. Maybe even too much.

Sources

Share this article:

Comments (2)

  • piter

    06 maja 2025 o 09:50

    Zastanawiam się w jakich przypadkach wykorzystanie defaultProps jest dalej niezbędne? https://github.com/facebook/react/pull/25699

  • Mateusz

    07 maja 2024 o 05:58

    Dzięki, fajny artykuł :)

You may be interested in

If this article interested you, check out other materials related to it thematically. Below you will find articles and podcast episodes authored by me, as well as books I recommend that expand on this topic.

Optimizing with Memoization by jarmoluk
Article
2026-06-14

Optimizing with Memoization

Cache is tempting because it sounds like a quick answer to unoptimized code. But remembering the result is only the beginning. You still need to know what the key is, when the result becomes invalid, and whether you might return data to the wrong user. That is what memoization is about.

Read more
Matryoshka Dolls by Frankenvrij
Article
2021-08-13

Currying

Functional programming is almost as popular as object-oriented programming. Many concepts from object-oriented programming have entered programming in general so deeply that sometimes we no longer even notice where a given approach comes from. Functional programming also has its interesting concepts, and currying is one of them. In this article I use examples to show how currying works and what problems it can help us solve.

Read more
React portals by Thomas Piekunka
Article
2022-10-14

Through Portals in React

Interplanetary travel is not exactly available or popular yet. Technically speaking, we can run into similar difficulties when travelling between different DOM structures in SPA applications. Today, let us look at how React solves this problem.

Read more

Zapisz się do newslettera

Bądź na bieżąco z nowymi materiałami, ćwiczeniami i ciekawostkami ze świata IT. Dołącz do mnie.