How to use React.FC<props> type when the children can either be a React node or a function


The React.FC type is the cause for above error:

  1. It already includes default children typed as ReactNode, which get merged (&) with your own children type contained in Props.
  2. ReactNode is a fairly wide type limiting the compiler's ability to narrow down the children 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.


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