Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove React.FC from Typescript template #8177

Merged
merged 1 commit into from Jan 22, 2020
Merged

Commits on Dec 13, 2019

  1. 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 = () => {
    	<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:
    ```ts
    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`.
    
    ```ts
    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):
    
    ```jsx
    <Select>
    	<Select.Item />
    </Select>
    ```
    
    This is possible, but awkward, with `React.FC`:
    
    ```tsx
    const  Select: React.FC<SelectProps> & { Item: React.FC<ItemProps> } = (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) => (<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.
    
    ##### 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<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:
    
    ```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 = () => <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:
    
    ```ts
    const Component1 = (props: ComponentProps): ReactNode => { /*...*/ }
    const Component2: FC<ComponentProps> = (props) => { /*...*/ }
    ```
    Retsam committed Dec 13, 2019
    Copy the full SHA
    39b216a View commit details
    Browse the repository at this point in the history