Our Blog
We help junior tech professionals, such as developers and designers, to grow.
Rethinking TypeScript Practices: The Downside of React.FC
Mariana Caldas 2023-06-20
In recent years, using TypeScript with React has become increasingly popular. TypeScript adds static typing to JavaScript, making it more robust and developer-friendly. One of TypeScript's constructs for React components is React.FC
, which stands for "React Function Component". This type definition has been widely used, but there's a new school of thought that suggests avoiding it. Let's discuss why.
What is React.FC?
In TypeScript, React.FC
or React.FunctionComponent
is a type that represents a function component. It can be used like this:
const MyComponent: React.FC = () => {
return <div>My Component</div>;
};
The Downside of React.FC
While React.FC
seems convenient, it has certain downsides that might make you reconsider its usage. These were highlighted in a Pull Request on the Create React App repository back in 2019.
Implicit Definition of Children
When you use React.FC
, it implicitly includes children
in the props, even if you don't need them. It means all components accept children
as a prop. This can lead to unwanted renderings in your component and is not caught by TypeScript due to React.FC
.
const App: React.FC = () => <div>Hello World</div>;
// Even though `App` doesn't expect children, it will still render them.
const Example = () => (
<App>
<div>Unwanted children</div>
</App>
);
The Example
component will render "Unwanted children" even though App
is not designed to accept children
.
Doesn't Support Generics
React.FC does not preserve unresolved generics, so you can't create a generic component like this one that accepts a prop
of any type and a callback
function to handle the prop:
type GenericComponentProps<T> = {
prop: T;
callback: (t: T) => void;
}
// The following will cause an error
const GenericComponent: React.FC<GenericComponentProps> = ({ prop, callback }) => {
// Some component logic here
};
Makes "component as namespace pattern" more awkward
This pattern, which is quite popular, allows a component to be used as a namespace for related components. Using React.FC
makes this awkward:
type SelectProps = { /* ... */ };
type ItemProps = { /* ... */ };
// With React.FC
const Select: React.FC<SelectProps> & { Item: React.FC<ItemProps> } = (props) => {/* ... */ }
Select.Item = (props) => { /*...*/ }
// Without React.FC
const Select = (props: SelectProps) => {/* ... */}
Select.Item = (props: ItemProps) => { /*...*/ }
Doesn't Work Correctly with defaultProps
The defaultProps
mechanism in React is used to define default values for props
in a component. It is useful when you want some props
to have a default value, but also want to allow consumers of your component to override these defaults.
The issue with React.FC arises when we combine TypeScript and defaultProps.
type ComponentProps = { name: string; }
const Component: React.FC<ComponentProps> = ({ name }) => (<div>{name.toUpperCase()}</div>);
Component.defaultProps = { name: "John" };
// usage
<Component /> // TypeScript error: Property 'name' is missing...
In this example, TypeScript considers name
as required and doesn't acknowledge that defaultProps
provides a default value for it. So, when we try to use <Component />
without providing a name prop, TypeScript will give an error, even though this should be valid due to the defaultProps.
The Alternative to React.FC
A cleaner way to define function components in TypeScript is to type the props and the function's return value directly:
type MyComponentProps = {
/* define your prop types here */
};
const MyComponent = (props: MyComponentProps): JSX.Element => {
/* ... */
};
This alternative doesn't have the downsides associated with React.FC
. It doesn't implicitly include children
, supports generics, allows the "component as namespace" pattern, and works correctly with defaultProps
.
Conclusion
Navigating the realm of TypeScript with React can feel a bit like deciphering a new language. On our journey, we initially encounter React.FC
, a seemingly helpful guide that promises to make our path smoother. However, as we dig deeper and our understanding grows, we come to recognize its limitations - its automatic inclusion of children
props, the lack of generics support, its awkward relationship with the 'component as namespace' pattern, and its deficiency in handling defaultProps
effectively.
In software development, each tool we use, each methodology we adopt, is in service of creating efficient, reliable, and maintainable code. We owe it to ourselves and to the robust applications we aim to build, to select the most reliable and beneficial practices.
Instead of React.FC
, a more flexible and robust alternative presents itself—directly typing the component function and its props. This approach doesn't suffer from the constraints of React.FC
and supports our components in all their various forms and functionalities.
Let's remember, the landscape of technology is always shifting. The tools and approaches that seemed infallible yesterday might be revealed as imperfect today. This doesn't negate their prior value but instead underscores the evolutionary nature of software development. The shift away from React.FC
towards more explicit typing isn't a dismissal, but rather a symbol of our growing understanding and maturity in using TypeScript with React.
As we continue our journey through the diverse landscape of TypeScript and React, let's stay adaptable, keep our knowledge updated, and strive for the practices that enable us to build better, more reliable software. Even if it means parting ways with some old habits, the benefits gained from this evolution will be worth the effort. Let the dance of development continue!