DEV Community

Cover image for Rethinking TypeScript Practices: The Downside of React.FC
Mariana Caldas for Web Dev Path

Posted on

Rethinking TypeScript Practices: The Downside of React.FC

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>;
};
Enter fullscreen mode Exit fullscreen mode

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>
);

Enter fullscreen mode Exit fullscreen mode

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
};

Enter fullscreen mode Exit fullscreen mode

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) => { /*...*/ }

Enter fullscreen mode Exit fullscreen mode

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...

Enter fullscreen mode Exit fullscreen mode

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 => {
  /* ... */
};

Enter fullscreen mode Exit fullscreen mode

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!

Top comments (0)