Skip to content

Commit

Permalink
Remove React.FC from Typescript template (#8177)
Browse files Browse the repository at this point in the history
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) => { /*...*/ }
```
  • Loading branch information
Retsam authored and ianschmitz committed Jan 22, 2020
1 parent a608c5a commit dada035
Showing 1 changed file with 1 addition and 1 deletion.
2 changes: 1 addition & 1 deletion packages/cra-template-typescript/template/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import logo from './logo.svg';
import './App.css';

const App: React.FC = () => {
const App = () => {
return (
<div className="App">
<header className="App-header">
Expand Down

0 comments on commit dada035

Please sign in to comment.