From 39b216ad91cce43abbc943c944f9500c6a9907d6 Mon Sep 17 00:00:00 2001 From: Retsam Date: Fri, 13 Dec 2019 15:27:41 -0500 Subject: [PATCH] Remove React.FC from Typescript template 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 ##### Provides an implicit definition of `children` 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: ```ts const App: React.FC = () => { /*... */ }; const Example = () => {
Unwanted children
} ``` 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: ```ts type GenericComponentProps = { prop: T callback: (t: T) => void } const GenericComponent = (props: GenericComponentProps) => {/*...*/} ``` 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`. ```ts const GenericComponent: React.FC = (props: GenericComponentProps) => {/*...*/} ``` ##### 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): ```jsx ``` This is possible, but awkward, with `React.FC`: ```tsx const Select: React.FC & { Item: React.FC } = (props) => {/* ... */ } Select.Item = (props) => { /*...*/ } ``` but "just works" without `React.FC`: ```tsx const Select = (props: SelectProps) => {/* ... */} Select.Item = (props) => { /*...*/ } ``` ##### 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... ```tsx type ComponentProps = { name: string; } const Component = ({ name }: ComponentProps) => (
{name.toUpperCase()} /* Safe since name is required */
); Component.defaultProps = { name: "John" }; const Example = () => () /* 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. ##### It's as long, or longer than the alternative: (especially longer if you use `FunctionalComponent`): Not a huge point, but it isn't even shorter to use `React.FC` ```ts const C1: React.FC = (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: ```ts 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: ```ts const Example = () => ; // Error here, due to Component returning the wrong thing ``` But even with explicit type annotations, `React.FC` still isn't saving very much boilerplate: ```ts const Component1 = (props: ComponentProps): ReactNode => { /*...*/ } const Component2: FC = (props) => { /*...*/ } ``` --- packages/cra-template-typescript/template/src/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cra-template-typescript/template/src/App.tsx b/packages/cra-template-typescript/template/src/App.tsx index 226ee6316af..eb557394f87 100644 --- a/packages/cra-template-typescript/template/src/App.tsx +++ b/packages/cra-template-typescript/template/src/App.tsx @@ -2,7 +2,7 @@ import React from 'react'; import logo from './logo.svg'; import './App.css'; -const App: React.FC = () => { +const App = () => { return (