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.

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
$age = 32;
getType($age); // integer
if ($age > 18) {
$age = "adult";
}
getType($age) // stringAs you can see, freely changing the type did not cause any error here. It would work similarly in JavaScript.
let age = 32;
typeof age; // number
if (age > 18) {
age = 'adult';
}
typeof age; // stringThis 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.
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?
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.
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.
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:
// 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.
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.
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.
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.
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
- Why You Should Probably Think Twice About Using React.FC
- Typechecking With PropTypes
- Function Components
- Using React.VFC Instead of React.FC
- Don't use Function Components, use Void Function Components
- Moving away from React.FC and React.VFC
- Should you use React.FC for typing React Components
- Why you probably shouldn’t use React.FC to type your React components
- TypeScript + React: Why I don't use React.FC
- Remove React.FC from Typescript template
- Update to allow components to render undefined
- Dynamicznie typy danych to nie zaleta, ale problem
- Dynamiczne typowanie w JavaScript
- Using the React children prop with TypeScript




Comments (2)
piter
06 maja 2025 o 09:50Zastanawiam się w jakich przypadkach wykorzystanie defaultProps jest dalej niezbędne? https://github.com/facebook/react/pull/25699
Mateusz
07 maja 2024 o 05:58Dzięki, fajny artykuł :)