How to use React.FC<props> type when the children can either be a React node or a function
tldr:
The React.FC
type is the cause for above error:
- It already includes default
children
typed asReactNode
, which get merged (&
) with your ownchildren
type contained inProps
. ReactNode
is a fairly wide type limiting the compiler's ability to narrow down thechildren
union type to a callable function in combination with point 1.
A solution is to omit FC
and use a more narrow type than ReactNode
to benefit type safety:
type Renderable = number | string | ReactElement | Renderable[]
type Props = {
children: ((x: number) => Renderable) | Renderable;
};
More details
First of all, here are the built-in React types:
type ReactText = string | number;
type ReactChild = ReactElement | ReactText;
interface ReactNodeArray extends Array<ReactNode> {}
type ReactFragment = {} | ReactNodeArray;
type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean
| null | undefined;
interface FunctionComponent<P = {}> {
(props: PropsWithChildren<P>, context?: any): ReactElement | null;
propTypes?: WeakValidationMap<P>;
contextTypes?: ValidationMap<any>;
defaultProps?: Partial<P>;
displayName?: string;
}
type PropsWithChildren<P> = P & { children?: ReactNode };
1.) You use FC<Props>
to type Comp
. FC
internally already includes a children
declaration typed as ReactNode
, which gets merged with children
definition from Props
:
type Props = { children: ((x: number) => ReactNode) | ReactNode } &
{ children?: ReactNode }
// this is how the actual/effective props rather look like
2.) Looking at ReactNode
type, you'll see that types get considerably more complex. ReactNode
includes type {}
via ReactFragment
, which is the supertype of everything except null
and undefined
. I don't know the exact decisions behind this type shape, microsoft/TypeScript#21699 hints at historical and backward-compatiblity reasons.
As a consequence, children
types are wider than intended. This causes your original errors: type guard typeof props.children === "function"
cannot narrow the type "muddle" properly to function
anymore.
Solutions
Omit React.FC
In the end, React.FC
is just a function type with extra properties like propTypes
, displayName
etc. with opinionated, wide children
type. Omitting FC
here will result in safer, more understandable types for compiler and IDE display. If I take your definition Anything that can be rendered
for children
, that could be:
import React, { ReactChild } from "react";
// You could keep `ReactNode`, though we can do better with more narrow types
type Renderable = ReactChild | Renderable[]
type Props = {
children: ((x: number) => Renderable) | Renderable;
};
const Comp = (props: Props) => {...} // leave out `FC` type
Custom FC
type without children
You could define your own FC
version, that contains everything from React.FC
except those wide children
types:
type FC_NoChildren<P = {}> = { [K in keyof FC<P>]: FC<P>[K] } & // propTypes etc.
{ (props: P, context?: any): ReactElement | null } // changed call signature
const Comp: FC_NoChildren<Props> = props => ...
Playground sample