hrtyy.dev

React.Children API

Code reading of React.Children API

React.Children API is used to manipulate props.children in React. For example, React.Children.count can be used to count child nodes up. This Sample component will show Empty when there are no children, but if they exist, it will show them.

1import React, {PropsWithChildren} from "react";
2
3export const Sample: React.FC<PropsWithChildren<unknown>> = ({children}) => (
4 <div>
5 {React.Children.count(children) === 0 ? (
6 <p>Empty</p>
7 ) : children}
8 </div>
9)

Examples

1// passing string
2<Sample>
3 Hello
4</Sample>
Result:

Hello

1// passing array
2<Sample>
3 {[1, 2].map(num => (
4 <p key={num}>{num}</p>
5 ))}
6</Sample>
Result:

1

2

1// passing null
2<Sample>
3 {null}
4</Sample>
Result:

// Empty

1// passing empty array
2<Sample>
3 {[].map(num => (
4 <p>{num}</p>
5 ))}
6</Sample>
Result:

// Empty


As the above examples show, React.Children.count returns 0 not only for empty array but also null (This also applies to undefined.).

Code Reading

First, check the type of props.children.

@types/react/index.d.ts
1type PropsWithChildren<P> = P & { children?: ReactNode };

It is ReactNode, then what is this?

@types/react/index.d.ts
1//
2// React Nodes
3// http://facebook.github.io/react/docs/glossary.html
4// ----------------------------------------------------------------------
5
6type ReactText = string | number;
7type ReactChild = ReactElement | ReactText;
8
9interface ReactNodeArray extends Array<ReactNode> {}
10type ReactFragment = {} | ReactNodeArray;
11type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;

ReactNode is the union type of many types including primitive types such as string, null and object types such as ReactElement. (Roughly, ReactElement is the type represents JSX) Arrays can be children thanks to ReactFragment.

Next, check the type of argument of React.Children.count function.

@types/react/index.d.ts
1//
2// React.Children
3// ----------------------------------------------------------------------
4
5interface ReactChildren {
6 map<T, C>(children: C | C[], fn: (child: C, index: number) => T):
7 C extends null | undefined ? C : Array<Exclude<T, boolean | null | undefined>>;
8 forEach<C>(children: C | C[], fn: (child: C, index: number) => void): void;
9 count(children: any): number;
10 only<C>(children: C): C extends any[] ? never : C;
11 toArray(children: ReactNode | ReactNode[]): Array<Exclude<ReactNode, boolean | null | undefined>>;
12}

React.Children.count receives any!!.

According to this official document, React.Children APIs are in packages/react.

React.Children.count function is here in React.js. This count function seems to be imported from ReactChildren.

React.js
1import {forEach, map, count, toArray, only} from './ReactChildren';

Seeing ReactChildren.js, you can see countChildren is exported as count. We found the implementation of React.Children.count.

ReactChildren.js:L248
1function countChildren(children: ?ReactNodeList): number {
2 let n = 0;
3 mapChildren(children, () => {
4 n++;
5 // Don't return anything
6 });
7 return n;
8}

This function just counts children up using mapChildren. In this function, props.children is converted to array of ReactNode by mapIntoArray.


The implementation of the function is a little bit long. Roughly, it checks the type of children and converts into array. If children is Array, mapIntoArray is called recursively.

Type checking whether children is null, string or Array is performed here.


Whatever the type is, children will be converted to Array. That's why React.Children.count can be used as I introduced first. (Strictly, non-Array objects such as Map, simple object are not supported.)

Reference

If you have any questions, please send an email tohoritayuya@gmail.com.