-
-
Notifications
You must be signed in to change notification settings - Fork 27k
Commit dada035
Remove React.FC from Typescript template (#8177)
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) => { /*...*/ }
```1 parent a608c5a commit dada035Copy full SHA for dada035
File tree
1 file changed
+1
-1
lines changedFilter options
- packages/cra-template-typescript/template/src
1 file changed
+1
-1
lines changedpackages/cra-template-typescript/template/src/App.tsx
Copy file name to clipboardExpand all lines: packages/cra-template-typescript/template/src/App.tsx+1-1
Original file line number | Diff line number | Diff line change | |
---|---|---|---|
| |||
2 | 2 |
| |
3 | 3 |
| |
4 | 4 |
| |
5 |
| - | |
| 5 | + | |
6 | 6 |
| |
7 | 7 |
| |
8 | 8 |
| |
|
0 commit comments