21
Should you use React.FC to type your components?
const Dialog: React.FC = ({children}) => {
return (
...
)
}
The consensus in the React community now seems to be that, you should avoid using React.FC
because it causes the component to implicitly take children
. This means the component will accept children, even if they're not supposed to.
Let's take a look at the pros and cons of React.FC
.
In addition to the children
prop, React.FC
declares an explicit return type for the component. The return type is ReactElement | null;
and we will get a type error if we try to return something else from the component.
const Header: React.FC = () => {
return "test";
)
}
/*
Type '() => string' is not assignable to type 'FC<{}>'.
Type 'string' is not assignable to type 'ReactElement<any, any> | null'.ts(2322)
*/
Here's the full definition of React.FunctionComponent
which React.FC
is a shorthand for.
interface FunctionComponent<P = {}> {
(props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
propTypes?: WeakValidationMap<P> | undefined;
contextTypes?: ValidationMap<any> | undefined;
defaultProps?: Partial<P> | undefined;
displayName?: string | undefined;
}
In addition to the function signature, React.FC
provides type checking for some static properties. propTypes
and defaultProps
define the prop types based on the generic type P
. These provide type validation for props you pass to the component and default prop values you can define with Component.defaultProps = {}
.
contextTypes
and displayName
are less useful in most situations.
contextTypes
is related to a legacy Context.
displayName
is used in debug messages.
The biggest downside to using React.FC
is that it causes the component to implicitly take children. This means all components defined using React.FC
will happily accept children, even when they're not supposed to.
const Icon: React.FC = <span className="icon">...</span>;
const OhNoh = <Icon>This should be a error</Icon>;
This won't be a runtime error, but TypeScript would catch this error if React.FC
had not been used.
React.FC
also doesn't accept generics (GeneriComponent<T>
) and makes using the "component as namespace" pattern awkward to type.
<Dialog>
<Dialog.Content></Dialog.Content>
</Dialog>;
const Dialog: React.FC<DialogProps> & { Content: React.FC<ContentProps> } = (
props
) => {
/*..*/
};
Dialog.Content = (props) => {
/*...*/
};
//vs
const Dialog = (props: DialogProps) => {
/*...*/
};
Dialog.Content = (props: ContentProps) => {
/*..*/
};
If you want to have the benefits of explicit return types, you can use JSX.Element
to explicitly type the return value of your components. children
should be typed with React.ReactNode
since it accepts all valid children values
interface Props {
children: React.ReactNode;
}
const Header = ({children}: Props): JSX.Element => {
return "test";
)
}
/*
Type 'string' is not assignable to type 'Element'.ts(2322)
*/
This is my preferred way of typing React components. I find the implicit return type is usually not a problem and haven't had any bugs where a component is returning something it's not supposed to.
interface Props {
children: React.ReactNode;
}
function Header({children}: Props) {
...
}
In my opinion, this is a pretty clear and clean way of typing React components. What this way loses in typesafe, it gains in readability. I also prefer using named functions because I think they are more readable than the array functions.
Photo by Ash Edmonds on Unsplash
21