156

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"]
}
Michael Joseph Aubry
  • 12,282
  • 16
  • 70
  • 135

9 Answers9

205

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.

Dan Abramov
  • 264,556
  • 84
  • 409
  • 511
  • I know this is a bit off-topic, but do you think `ref` will ever be a "regular prop" too? Typing `ref`, especially for components wrapping other components, is usually very difficult. `React.ComponentProps` would solve it if `ref` were a normal prop. – Fernando Rojo Apr 09 '22 at 17:00
  • 4
    At some point, I think so — there are some changes we need to make before we can do that. I don't see why typing it would be complicated though. That's probably a separate discussion and you're welcome to start it in the DefinitelyTyped repo. – Dan Abramov Apr 09 '22 at 17:06
  • 1
    That's good to hear. The issue is related to typing `forwardRef` for a design system library. For `const Box = styled(View)`, I'm not sure how to make `Box` have the same ref type as `View`, whereas for the props this is easy with a generic. I'll continue this on the DefinitelyTyped repo though, thanks for the prompt response! – Fernando Rojo Apr 09 '22 at 17:15
  • Thanks for the help/answer. Where would one make this change in the code to work as it used to – bombillazo Apr 14 '22 at 01:23
  • 2
    What about `VFC`? It all seemed pretty straightforward: `FC` added implicit `children`, `VFC` did not. Now in React 18 it seems both are the same because...? It looks like I'll be creating a new `type CFC = FC>` to restore that distinction. – dalmo Apr 20 '22 at 05:11
  • @DanAbramov: can we get a `React.FCC = React.FC>` so that we are on-parr with the old interface? This is not an uncommon use case to just have `React.FCC`. – Tianhui Li Apr 22 '22 at 23:38
  • @DanAbramov 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 }
    – Scymex Apr 29 '22 at 15:16
  • 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. – Scymex Apr 29 '22 at 15:25
  • @dalmo React.VFC is deprecated in React 18: https://github.com/DefinitelyTyped/DefinitelyTyped/pull/59882 – Ian Kim May 18 '22 at 19:54
  • This breaking change is really terrible. A lot of teams explicitly used FC to imply "adds a children prop", and used VFC to imply "doesn't add a children prop", and while the above fixes can work, in theory, in reality, it's much much more complicated, as a lot of _libraries_ made the same assumption. Please please please consider reverting this change. – Matthew Dean Oct 21 '22 at 18:23
  • you probably don't want to use ʼimport * as React ʼ (for reference https://stackoverflow.com/a/55287189/1701178) – dnanon Mar 01 '23 at 21:19
69

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

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}) => {...}

With props

Before

import React from 'react';

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

After

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.

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>
simPod
  • 11,498
  • 17
  • 86
  • 139
  • The first option has ts error `Generic type 'PropsWithChildren' requires 1 type argument(s).`, but the second one is just perfect – Laszlo Sarvold Apr 08 '22 at 16:26
  • 1
    @LaszloSarvold I've reflected your comment, thx. – simPod Apr 08 '22 at 16:32
  • 3
    This is great, what about third party libraries? I've been hacking it `const ThirdPartyHack: any = ThirdParty` – Michael Joseph Aubry Apr 08 '22 at 17:42
  • @MichaelJosephAubry unfortunately, you'll have to wait for libraries to adapt this (my answer ideally :D) – simPod Apr 08 '22 at 19:17
  • Note you don't have to use this PropsWithChildren helper. You can just define children manually, like any other prop. I'd say this is a clearer and simpler approach because it doesn't introduce new concepts. https://stackoverflow.com/a/71809927/458193 – Dan Abramov Apr 09 '22 at 16:32
  • 1
    This is breaking the React "promise" of backward compatibility from one version to the next, at least in regards with TypeScript typings. Indeed, one can't update to the new typings if only one of their dependency is still on React 17. So if you have dependencies, you can only: - Update them all only when they are all using React 18 typings - Or not updating ANYTHING at all, because you can't just update one dependency that's on React 18 while the rest is on React 17 (again, only talking about typings). – Antoine Jaussoin Apr 09 '22 at 18:32
  • @AntoineJaussoin Strictly saying, there is no "promise of backwards compatibility" between major versions — major versions _are_ for making backwards-incompatible changes. While we've tried to make React 18 as easy to adopt as possible, if you decided to adopt TypeScript, sometimes you're going to have to deal with bugfixes in typings, and those may take time to propagate through the ecosystem. – Dan Abramov Apr 09 '22 at 19:33
  • @DanAbramov: fair, I was referring to the "Gradual Upgrades via Warnings" concept, where you only get warnings (at worst) when upgrading from one version to the next. I understand it doesn't apply to TypeScript though, but I am at least saying that this is going to impact a lot of people and it would have been beneficial to apply this "Gradual Upgrades via Warnings" policy to the typings as well. – Antoine Jaussoin Apr 10 '22 at 20:06
  • Unfortunately we’re pretty limited in what kind of warnings we can emit in TypeScript. We could _deprecate_ the children prop but of course that would be a lot more confusing. (The only problem was its wrong type.) – Dan Abramov Apr 11 '22 at 11:24
  • `extends PropsWithChildren` is the best answer> – El7or Jun 10 '22 at 13:32
  • Since June 2022 it is no longer necessary to write `PropsWithChildren` because [PR #60641][1] was merged that makes `unknown` the default value of the generic parameter to `PropsWithChildren`. [1]: https://github.com/DefinitelyTyped/DefinitelyTyped/pull/60641 – Jorrit Schippers Jul 15 '22 at 07:39
  • The "Suppress warnings for some reason" doesn't seem to work and I'm not sure why. I just get a different error -- `Property 'children' does not exist on type 'MyProps | PropsWithChildren'` – Matthew Dean Oct 21 '22 at 02:04
  • It does not exist on `MyProps`. Use only `PropsWithChildren` – simPod Oct 21 '22 at 07:35
  • I am only using `PropsWithChildren`, using the override listed above. I'm saying it still persists with this TypeScript error. The TS ambient declaration doesn't appear to have the desired effect. – Matthew Dean Oct 21 '22 at 18:22
  • @MatthewDean Were you able to get it working using "Suppress warnings for some reason" way. I am trying same way but getting similar Property 'children' does not exist on type 'IProps | PropsWithChildren'. Please see https://stackoverflow.com/questions/75695828/property-children-does-not-exist-on-type – ajm Mar 13 '23 at 06:08
11

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

Update #01

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;
  
  /* ... */
}
Garvae
  • 465
  • 4
  • 14
7

Create your custom functional component type (a modification of FC).

Lets name it 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>>

Whenever you want 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 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.

ashuvssut
  • 1,725
  • 9
  • 17
3

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.

Scymex
  • 954
  • 9
  • 17
2

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.

Derek Pollard
  • 6,953
  • 6
  • 39
  • 59
2

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.

Reactgular
  • 52,335
  • 19
  • 158
  • 208
1

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

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