'React 18 TypeScript children FC

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"]
}


Solution 1:[1]

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.

Before

import * as React from 'react';

type Props = {};
const Component: React.FC<Props> = ({children}) => {...}

After

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.

Solution 2:[2]


How to resolve

No props

Before

import React from 'react';

const Component: React.FC = ({children}) => {...}

After

Create e.g. react.d.ts to define your helper type 1

export type PropsWithChildrenOnly = PropsWithChildren<unknown>;
export type ReactFCWithChildren = React.FC<PropsWithChildrenOnly>;
import React from 'react';
import { PropsWithChildrenOnly, ReactFCWithChildren } from './react';

const Component: React.FC<PropsWithChildrenOnly> = ({children}) => {...}

or

const Component: ReactFCWithChildren = ({children}) => {...}

With props

Before

import React from 'react';

interface Props {
  ...
}
const Component: React.FC<Props> = ({children}) => {...}

After

import React, { PropsWithChildren } from 'react';

interface Props {
  ...
}
const Component: React.FC<PropsWithChildren<Props>> = ({children}) => {...}

or

interface Props extends PropsWithChildren<unknown> {
  ...
}

const Component: React.FC<Props> = ({children}) => {...}

1While 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.

Suppress warnings for some reason

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;
  }
}

Why did FC signature change

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>

Solution 3:[3]

For a long time i was using React.FC only when i wanted my component to accept children, and i did not need to declare it, because React.FC already has children. Now i have been told that children is removed because using React.FC assumes the component has children. well, that was my point.

Solution 4:[4]

Create you custom functional component type FC.

Lets name it FCC

// 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>>

Whenever you want chilren property in you Component's props, use it like this:

// import FCC from types.ts
const MyComponent: FCC = ({children}) => {
  return (
    <>{chilren}</>
  )
}

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 like ReactFCC<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.e P = {}) solves the issue.

Solution 5:[5]

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>>;

Solution 6:[6]

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.

Solution 7:[7]

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.

Component without children

import * as React from 'react';

type Props = {
};

export function Component({}: Props) {
  ...
}

<Component /> // Valid
<Component>test</Component> // Invalid

Component with children required

import * as React from 'react';

type Props = {
  children: React.ReactNode
};

export function Component({children}: Props) {
  ...
}

<Component>test</Component> // Valid
<Component /> // Invalid

Component with children optional

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.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3 Aziz
Solution 4
Solution 5 Garvae
Solution 6 Derek Pollard
Solution 7 Scymex