Created by: Retsam
This removes React.FC
from the base template for a Typescript project.
Long explanation for a small change:
React.FC
is unnecessary: it provides next to no benefits and has a few downsides. (See below.) I see a lot of beginners to TS+React using it, however, and I think that it's usage in this template is a contributing factor to that, as the prominence of this template makes it a de facto source of "best practice".
Downsides to React.FC/React.FunctionComponent
children
Provides an implicit definition of Defining a component with React.FC
causes it to implicitly take children
(of type ReactNode
). It means that all components accept children, even if they're not supposed to, allowing code like:
const App: React.FC = () => { /*... */ };
const Example = () => {
<App><div>Unwanted children</div></App>
}
This isn't a run-time error, but it is a mistake and one that would be caught by Typescript if not for React.FC
.
Doesn't support generics.
I can define a generic component like:
type GenericComponentProps<T> = {
prop: T
callback: (t: T) => void
}
const GenericComponent = <T>(props: GenericComponentProps<T>) => {/*...*/}
But it's not possible when using React.FC
- there's no way to preserve the unresolved generic T
in the type returned by React.FC
.
const GenericComponent: React.FC</* ??? */> = <T>(props: GenericComponentProps<T>) => {/*...*/}
Makes "component as namespace pattern" more awkward.
It's a somewhat popular pattern to use a component as a namespace for related components (usually children):
<Select>
<Select.Item />
</Select>
This is possible, but awkward, with React.FC
:
const Select: React.FC<SelectProps> & { Item: React.FC<ItemProps> } = (props) => {/* ... */ }
Select.Item = (props) => { /*...*/ }
but "just works" without React.FC
:
const Select = (props: SelectProps) => {/* ... */}
Select.Item = (props: ItemProps) => { /*...*/ }
Doesn't work correctly with defaultProps
This is a fairly moot point as in both cases it's probably better to use ES6 default arguments, but...
type ComponentProps = { name: string; }
const Component = ({ name }: ComponentProps) => (<div>
{name.toUpperCase()} /* Safe since name is required */
</div>);
Component.defaultProps = { name: "John" };
const Example = () => (<Component />) /* Safe to omit since name has a default value */
This compiles correctly. Any approach with React.FC
will be slightly wrong: either React.FC<{name: string}>
will make the prop required by consumers, when it should be optional, or React.FC<{name?: string}>
will cause name.toUpperCase()
to be a type error. There's no way to replicate the "internally required, externally optional" behavior which is desired.
FunctionalComponent
):
It's as long, or longer than the alternative: (especially longer if you use Not a huge point, but it isn't even shorter to use React.FC
const C1: React.FC<CProps> = (props) => { }
const C2 = (props: CProps) => {};
Benefits of React.FC
Provides an explicit return type
The only benefit I really see to React.FC
(unless you think that implicit children
is a good thing) is that it specifies the return type, which catches mistakes like:
const Component = () => {
return undefined; // components aren't allowed to return undefined, just `null`
}
In practice, I think this is fine, as it'll be caught as soon as you try to use it:
const Example = () => <Component />; // Error here, due to Component returning the wrong thing
But even with explicit type annotations, React.FC
still isn't saving very much boilerplate:
const Component1 = (props: ComponentProps): JSX.Element => { /*...*/ }
const Component2: FC<ComponentProps> = (props) => { /*...*/ }