Michael Joseph Aubry
Michael Joseph Aubry

Reputation: 13412

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

Upvotes: 180

Views: 168945

Answers (10)

Prasath S
Prasath S

Reputation: 4396

Just import like this

import * as React from 'react';

Upvotes: -1

simPod
simPod

Reputation: 13456

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>

Upvotes: 93

Reactgular
Reactgular

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

Malin Samaranayake
Malin Samaranayake

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

Garvae
Garvae

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

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;
  
  /* ... */
}

Upvotes: 12

ashuvssut
ashuvssut

Reputation: 2275

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.

Upvotes: 8

Arash Milani
Arash Milani

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

Scymex
Scymex

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.

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.

Upvotes: 3

Dan Abramov
Dan Abramov

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.

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.

Upvotes: 242

Derek Pollard
Derek Pollard

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

Related Questions