Reputation: 13412
I upgraded to React 18 and things compiled fine. Today it seems every single component that uses children is throwing an error. Property 'children' does not exist on type 'IPageProps'.
Before children props were automatically included in the FC
interface. Now it seems I have to manually add children: ReactNode
. What is the correct typescript type for react children?
Is this part of the React 18 update, or is something screwed up in my env?
package.json
"react": "^18.0.0",
"react-dom": "^18.0.0",
"next": "12.1.4",
"@types/react": "18.0.0",
"@types/react-dom": "18.0.0",
tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
"alwaysStrict": true,
"sourceMap": true,
"incremental": true
},
"include": ["src"],
"exclude": ["node_modules"]
}
Upvotes: 180
Views: 168945
Reputation: 13456
import React from 'react';
const Component: React.FC = ({children}) => {...}
Create e.g. react.d.ts to define your helper type 1
import React from 'react';
export type ReactFCWithChildren = React.FC<PropsWithChildren>;
import {ReactFCWithChildren } from './react';
const Component: ReactFCWithChildren = ({children}) => {...}
or
import React from 'react';
const Component: React.FC<React.PropsWithChildren> = ({children}) => {...}
import React from 'react';
interface Props {
...
}
const Component: React.FC<Props> = ({children}) => {...}
import React from 'react';
interface Props {
...
}
const Component: React.FC<React.PropsWithChildren<Props>> = ({children}) => {...}
or
import React from 'react';
interface Props extends React.PropsWithChildren {
...
}
const Component: React.FC<Props> = ({children}) => {...}
1 While defining children
manually seems easy, it's better to leverage types that are already prepared for you in @types package. When there are changes to the type in the future, it will automatically propagate from the lib everywhere in your code so you won't have to touch it yourself.
You can override react types by creating react.d.ts
file with following definition which would revert the type to @types/react v17
import * as React from '@types/react';
declare module 'react' {
interface FunctionComponent<P = {}> {
(props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
}
}
children
prop was removed from React.FunctionComponent
(React.FC
) so you have to declare it explicitly.
TS will tell you errors like
Type '{ children: ...; }' has no properties in common with type 'IntrinsicAttributes'."
You can read why here. TLDR it prevents bugs like
const ComponentWithNoChildren: React.FC = () => <>Hello</>;
...
<ComponentWithNoChildren>
// passing children is wrong since component does not accept any
<UnusedChildrenSinceComponentHasNoChildren />
</ComponentWithNoChildren>
Upvotes: 93
Reputation: 54771
You can declare FC17/VFC17 types that are backwards compatiable.
Add the following file to your project.
types.d.ts
import {FunctionComponent, PropsWithChildren} from 'react';
declare module 'react' {
type FC17<P = {}> = FunctionComponent<PropsWithChildren<P>>;
type VFC17<P = {}> = FunctionComponent<P>;
}
You can now search/replace all occurrences of FC and VFC in your source code to use the new types. Make sure to use case and exact word matching.
Your component code should end up like this:
import {FC17} from 'react';
export const MyComponent: FC17 = ({children}) => {
return <div>{children}</div>;
};
You can now continue to work, and progressively modify your code to use the new React 18 types anyway you want.
Upvotes: 2
Reputation: 41
In my opinion, it's better to avoid the 'FunctionComponent'/'FC' and do the following. this avoids one extra burden of being complied to the 'FunctionComponent' type declaration
import React, {ReactNode} from 'react';
interface Props {
children: ReactNode;
}
function Component({children}: Props):JSX.Element {
return (
<div>{children}</div>
);
}
export default Component;
Upvotes: 1
Reputation: 728
Yes, the React.FC type has changed. But you can declare your own type with blackjack and react children.
My way is to create src/types/react.d.ts
with content like this:
import React, { PropsWithChildren } from 'react';
export type ReactFCC<T> = React.FC<PropsWithChildren<T>>;
You can add default value for the T
parameter:
import React, { PropsWithChildren } from 'react';
export type ReactFCC<T = Record<string, unknown>> = React.FC<PropsWithChildren<T>>;
or
import React, { PropsWithChildren } from 'react';
export type ReactFCC<T = unknown> = React.FC<PropsWithChildren<T>>;
You can now choose not to specify a type in a ReactFCC
generic without warnings.
Before:
export const Component: ReactFCC<SomeType> = props => {
const { children } = props;
/* ... */
}
After:
export const Component: ReactFCC = props => {
const { children } = props;
/* ... */
}
Upvotes: 12
Reputation: 2275
FC
).FCC
(Denoting:- Functional component with children ;) )// Put this in your global types.ts
import { FC, PropsWithChildren } from "react";
// Custom Type for a React functional component with props AND CHILDREN
export type FCC<P={}> = FC<PropsWithChildren<P>>
children
property in you Component's props
, use it like this:// import FCC from types.ts
const MyComponent: FCC = ({children}) => {
return (
<>{children}</>
)
}
OR
interface MyCompoProps{
prop1: string
}
const MyComponent: FCC<MyCompoProps> = ({children, prop1}) => {
return (
<>{children}</>
)
}
PS This answer might look similar to @Garvae's answer but his
ReactFCC<P>
type
should be written likeReactFCC<P={}>
to prevent this following error:
Generic type 'ReactFCC' requires 1 type argument(s)
This error occurs when you are passing no props to the Component. children prop should be an optional prop. So giving those props a default
{}
value (i.eP = {}
) solves the issue.
Upvotes: 8
Reputation: 11
first, you have to define a global interface
import { PropsWithChildren } from "react";
interface ReactFC<T = {}> extends React.FC<PropsWithChildren<T>> {}
component's props
interface ReceiptProps {
onSubmitForm: () => void;
orderId: number;
}
const Receipt: ReactFC<ReceiptProps> = ({orderId, onSubmitForm,children }) => {
return <div> { children } </div>
}
Upvotes: 1
Reputation: 1074
As Dan points out in his answer, you may not need React.FC
any more. Here's an additional suggestion for if you choose to use a plain function
.
import * as React from 'react';
type Props = {
};
export function Component({}: Props) {
...
}
<Component /> // Valid
<Component>test</Component> // Invalid
import * as React from 'react';
type Props = {
children: React.ReactNode
};
export function Component({children}: Props) {
...
}
<Component>test</Component> // Valid
<Component /> // Invalid
import * as React from 'react';
type Props = {
children?: React.ReactNode
};
export function Component({children}: Props) {
...
}
<Component>test</Component> // Valid
<Component /> // Valid
Using React.ReactNode as return type doesn't seem like a good recommendation since rendering such a component from the top of another component results in: [tsserver 2786] [E] 'Example' cannot be used as a JSX component. Its return type 'ReactNode' is not a valid JSX element. Type 'undefined' is not assignable to type 'Element | null'. Try this: function Example (): React.ReactNode { return }; function Other(): React.ReactNode { return }
Omitting return type entirely might be better. tsserver will automatically warn about invalid return as long as there is code that tries to use the component. function Example () {}; You could put a usage in the same file like this, then tsserver will warn if invalid. You could even skip assigning to a variable.
Upvotes: 3
Reputation: 268245
Although this answer is correct, I want to note that you absolutely don't have to use this PropsWithChildren
helper. (It is primarily useful for the codemod, not manual usage.)
Instead, I find it easier to define them manually.
import * as React from 'react';
type Props = {};
const Component: React.FC<Props> = ({children}) => {...}
import * as React from 'react';
type Props = {
children?: React.ReactNode
};
const Component: React.FC<Props> = ({children}) => {...}
That is all that's needed.
Or you can stop using React.FC
altogether.
import * as React from 'react';
type Props = {
children?: React.ReactNode
};
function Component({children}: Props): React.ReactNode {
...
}
In React, children
is a regular prop and is not something special. So you need to define it just like you define all the other props. The previous typings that hid it were wrong.
Upvotes: 242
Reputation: 7165
It looks like the children
attribute on the typescript typings were removed.
I had to manually add children to my props; There is probably a better solution to fix this, but in the interim, this works.
Upvotes: 2