diff --git a/beta/src/content/apis/react/Children.md b/beta/src/content/apis/react/Children.md index ce3019d65..06cef1ae7 100644 --- a/beta/src/content/apis/react/Children.md +++ b/beta/src/content/apis/react/Children.md @@ -1,20 +1,939 @@ --- -title: React.Children +title: Children --- - + -This section is incomplete, please see the old docs for [React.Children.](https://reactjs.org/docs/react-api.html#reactchildren) - - +Using `Children` is uncommon and can lead to fragile code. [See common alternatives.](#alternatives) + -`React.Children` provides utilities for dealing with the this.props.children opaque data structure. +`Children` lets you manipulate and transform the JSX you received as the [`children` prop.](/learn/passing-props-to-a-component#passing-jsx-as-children) -See the [React.Children](https://reactjs.org/docs/react-api.html#reactchildren) docs for more info. +```js +const mappedChildren = Children.map(children, child => +
+ {child} +
+); + +```
+ +--- + +## Usage {/*usage*/} + +### Transforming children {/*transforming-children*/} + +To transform the children JSX that your component [receives as the `children` prop,](/learn/passing-props-to-a-component#passing-jsx-as-children) call `Children.map`: + +```js {6,10} +import { Children } from 'react'; + +function RowList({ children }) { + return ( +
+ {Children.map(children, child => +
+ {child} +
+ )} +
+ ); +} +``` + +In the example above, the `RowList` wraps every child it receives into a `
+
+

This is the first item.

+
+
+

This is the second item.

+
+
+

This is the third item.

+
+
+``` + +`Children.map` is similar to [to transforming arrays with `map()`.](/learn/rendering-lists) The difference is that the `children` data structure is considered *opaque.* This means that even if it's sometimes an array, you should not assume it's an array or any other particular data type. This is why you should use `Children.map` if you need to transform it. + + + +```js +import RowList from './RowList.js'; + +export default function App() { + return ( + +

This is the first item.

+

This is the second item.

+

This is the third item.

+
+ ); +} +``` + +```js RowList.js active +import { Children } from 'react'; + +export default function RowList({ children }) { + return ( +
+ {Children.map(children, child => +
+ {child} +
+ )} +
+ ); +} +``` + +```css +.RowList { + display: flex; + flex-direction: column; + border: 2px solid grey; + padding: 5px; +} + +.Row { + border: 2px dashed black; + padding: 5px; + margin: 5px; +} +``` + +
+ + + +In React, the `children` prop is considered an *opaque* data structure. This means that you shouldn't rely on how it is structured. To transform, filter, or count children, you should use the `Children` methods. + +In practice, the `children` data structure is often represented as an array internally. However, if there is only a single child, then React won't create an extra array since this would lead to unnecessary memory overhead. As long as you use the `Children` methods instead of directly introspecting the `children` prop, your code will not break even if React changes how the data structure is actually implemented. + +Even when `children` is an array, `Children.map` has useful special behavior. For example, `Children.map` combines the [keys](/learn/rendering-lists#keeping-list-items-in-order-with-key) on the returned elements with the keys on the `children` you've passed to it. This ensures the original JSX children don't "lose" keys even if they get wrapped like in the example above. + + + + + +The `children` data structure **does not include rendered output** of the components you pass as JSX. In the example below, the `children` received by the `RowList` only contains two items rather than three: + +1. `

This is the first item.

` +2. `` + +This is why only two row wrappers are generated in this example: + + + +```js +import RowList from './RowList.js'; + +export default function App() { + return ( + +

This is the first item.

+ +
+ ); +} + +function MoreRows() { + return ( + <> +

This is the second item.

+

This is the third item.

+ + ); +} +``` + +```js RowList.js +import { Children } from 'react'; + +export default function RowList({ children }) { + return ( +
+ {Children.map(children, child => +
+ {child} +
+ )} +
+ ); +} +``` + +```css +.RowList { + display: flex; + flex-direction: column; + border: 2px solid grey; + padding: 5px; +} + +.Row { + border: 2px dashed black; + padding: 5px; + margin: 5px; +} +``` + +
+ +**There is no way to get the rendered output of an inner component** like `` when manipulating `children`. This is why [it's usually better to use one of the alternative solutions.](#alternatives) + +
+ +--- + +### Running some code for each child {/*running-some-code-for-each-child*/} + +Call `Children.forEach` to iterate over each child in the `children` data structure. It does not return any value and is similar to the [array `forEach` method.](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach) You can use it to run custom logic like constructing your own array. + + + +```js +import SeparatorList from './SeparatorList.js'; + +export default function App() { + return ( + +

This is the first item.

+

This is the second item.

+

This is the third item.

+
+ ); +} +``` + +```js SeparatorList.js active +import { Children } from 'react'; + +export default function SeparatorList({ children }) { + const result = []; + Children.forEach(children, (child, index) => { + result.push(child); + result.push(
); + }); + result.pop(); // Remove the last separator + return result; +} +``` + +
+ + + +As mentioned earlier, there is no way to get the rendered output of an inner component when manipulating `children`. This is why [it's usually better to use one of the alternative solutions.](#alternatives) + + + +--- + +### Counting children {/*counting-children*/} + +Call `Children.count(children)` to calculate the number of children. + + + +```js +import RowList from './RowList.js'; + +export default function App() { + return ( + +

This is the first item.

+

This is the second item.

+

This is the third item.

+
+ ); +} +``` + +```js RowList.js active +import { Children } from 'react'; + +export default function RowList({ children }) { + return ( +
+

+ Total rows: {Children.count(children)} +

+ {Children.map(children, child => +
+ {child} +
+ )} +
+ ); +} +``` + +```css +.RowList { + display: flex; + flex-direction: column; + border: 2px solid grey; + padding: 5px; +} + +.RowListHeader { + padding-top: 5px; + font-size: 25px; + font-weight: bold; + text-align: center; +} + +.Row { + border: 2px dashed black; + padding: 5px; + margin: 5px; +} +``` + +
+ + + +As mentioned earlier, there is no way to get the rendered output of an inner component when manipulating `children`. This is why [it's usually better to use one of the alternative solutions.](#alternatives) + + + +--- + +### Converting children to an array {/*converting-children-to-an-array*/} + +Call `Children.toArray(children)` to turn the `children` data structure into a regular JavaScript array. This lets you manipulate the array with built-in array methods like [`filter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter), [`sort`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort), or [`reverse`.](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse) + + + +```js +import ReversedList from './ReversedList.js'; + +export default function App() { + return ( + +

This is the first item.

+

This is the second item.

+

This is the third item.

+
+ ); +} +``` + +```js ReversedList.js active +import { Children } from 'react'; + +export default function ReversedList({ children }) { + const result = Children.toArray(children); + result.reverse(); + return result; +} +``` + +
+ + + +As mentioned earlier, there is no way to get the rendered output of an inner component when manipulating `children`. This is why [it's usually better to use one of the alternative solutions.](#alternatives) + + + +--- + +## Alternatives {/*alternatives*/} + +### Exposing multiple components {/*exposing-multiple-components*/} + +Manipulating children with the `Children` methods often leads to fragile code. When you pass children to a component in JSX, you don't usually expect the component to manipulate or transform the individual children. + +When you can, try to avoid using the `Children` methods. For example, if you want every child of `RowList` to be wrapped in `
`, export a `Row` component, and manually wrap every row into it like this: + + + +```js +import { RowList, Row } from './RowList.js'; + +export default function App() { + return ( + + +

This is the first item.

+
+ +

This is the second item.

+
+ +

This is the third item.

+
+
+ ); +} +``` + +```js RowList.js +import { Children } from 'react'; + +export function RowList({ children }) { + return ( +
+ {children} +
+ ); +} + +export function Row({ children }) { + return ( +
+ {children} +
+ ); +} +``` + +```css +.RowList { + display: flex; + flex-direction: column; + border: 2px solid grey; + padding: 5px; +} + +.Row { + border: 2px dashed black; + padding: 5px; + margin: 5px; +} +``` + +
+ +Unlike using `Children.map`, this approach does not wrap every child automatically. **However, this approach has a significant benefit compared to the [earlier example with `Children.map`](#transforming-children) because it works even if you keep extracting more components.** For example, it still works if you extract your own `MoreRows` component: + + + +```js +import { RowList, Row } from './RowList.js'; + +export default function App() { + return ( + + +

This is the first item.

+
+ +
+ ); +} + +function MoreRows() { + return ( + <> + +

This is the second item.

+
+ +

This is the third item.

+
+ + ); +} +``` + +```js RowList.js +import { Children } from 'react'; + +export function RowList({ children }) { + return ( +
+ {children} +
+ ); +} + +export function Row({ children }) { + return ( +
+ {children} +
+ ); +} +``` + +```css +.RowList { + display: flex; + flex-direction: column; + border: 2px solid grey; + padding: 5px; +} + +.Row { + border: 2px dashed black; + padding: 5px; + margin: 5px; +} +``` + +
+ +This wouldn't work with `Children.map` because it would "see" `` as a single child (and a single row). + +--- + +### Accepting an array of objects as a prop {/*accepting-an-array-of-objects-as-a-prop*/} + +You can also explicitly pass an array as a prop. For example, this `RowList` accepts a `rows` array as a prop: + + + +```js +import { RowList, Row } from './RowList.js'; + +export default function App() { + return ( + This is the first item.

}, + { id: 'second', content:

This is the second item.

}, + { id: 'third', content:

This is the third item.

} + ]} /> + ); +} +``` + +```js RowList.js +export function RowList({ rows }) { + return ( +
+ {rows.map(row => ( +
+ {row.content} +
+ ))} +
+ ); +} +``` + +```css +.RowList { + display: flex; + flex-direction: column; + border: 2px solid grey; + padding: 5px; +} + +.Row { + border: 2px dashed black; + padding: 5px; + margin: 5px; +} +``` + +
+ +Since `rows` is a regular JavaScript array, the `RowList` component can use built-in array methods like [`map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) on it. + +This pattern is especially useful when you want to be able to pass more information as structured data together with children. In the below example, the `TabSwitcher` component receives an array of objects as the `tabs` prop: + + + +```js +import TabSwitcher from './TabSwitcher.js'; + +export default function App() { + return ( + This is the first item.

+ }, + { + id: 'second', + header: 'Second', + content:

This is the second item.

+ }, + { + id: 'third', + header: 'Third', + content:

This is the third item.

+ } + ]} /> + ); +} +``` + +```js TabSwitcher.js +import { useState } from 'react'; + +export default function TabSwitcher({ tabs }) { + const [selectedId, setSelectedId] = useState(tabs[0].id); + const selectedTab = tabs.find(tab => tab.id === selectedId); + return ( + <> + {tabs.map(tab => ( + + ))} +
+
+

{selectedTab.header}

+ {selectedTab.content} +
+ + ); +} +``` + +
+ +Unlike passing the children as JSX, this approach lets you associate some extra data like `header` with each item. Because you are working with the `tabs` directly, and it is an array, you do not need the `Children` methods. + +--- + +### Calling a render prop to customize rendering {/*calling-a-render-prop-to-customize-rendering*/} + +Instead of producing JSX for every single item, you can also pass a function that returns JSX, and call that function when necessary. In this example, the `App` component passes a `renderContent` function to the `TabSwitcher` component. The `TabSwitcher` component calls `renderContent` only for the selected tab: + + + +```js +import TabSwitcher from './TabSwitcher.js'; + +export default function App() { + return ( + { + return tabId[0].toUpperCase() + tabId.slice(1); + }} + renderContent={tabId => { + return

This is the {tabId} item.

; + }} + /> + ); +} +``` + +```js TabSwitcher.js +import { useState } from 'react'; + +export default function TabSwitcher({ tabIds, getHeader, renderContent }) { + const [selectedId, setSelectedId] = useState(tabIds[0]); + return ( + <> + {tabIds.map((tabId) => ( + + ))} +
+
+

{getHeader(selectedId)}

+ {renderContent(selectedId)} +
+ + ); +} +``` + +
+ +A prop like `renderContent` is called a *render prop* because it is a prop that specifies how to render a piece of the user interface. However, there is nothing special about it: it is a regular prop which happens to be a function. + +Render props are functions, so you can pass information to them. For example, this `RowList` component passes the `id` and the `index` of each row to the `renderRow` render prop, which uses `index` to highlight even rows: + + + +```js +import { RowList, Row } from './RowList.js'; + +export default function App() { + return ( + { + return ( + +

This is the {id} item.

+
+ ); + }} + /> + ); +} +``` + +```js RowList.js +import { Fragment } from 'react'; + +export function RowList({ rowIds, renderRow }) { + return ( +
+

+ Total rows: {rowIds.length} +

+ {rowIds.map((rowId, index) => + + {renderRow(rowId, index)} + + )} +
+ ); +} + +export function Row({ children, isHighlighted }) { + return ( +
+ {children} +
+ ); +} +``` + +```css +.RowList { + display: flex; + flex-direction: column; + border: 2px solid grey; + padding: 5px; +} + +.RowListHeader { + padding-top: 5px; + font-size: 25px; + font-weight: bold; + text-align: center; +} + +.Row { + border: 2px dashed black; + padding: 5px; + margin: 5px; +} + +.RowHighlighted { + background: #ffa; +} +``` + +
+ +This is another example of how parent and child components can cooperate without manipulating the children. + +--- + +## Reference {/*reference*/} + +### `Children.count(children)` {/*children-count*/} + +Call `Children.count(children)` to count the number of children in the `children` data structure. + +```js RowList.js active +import { Children } from 'react'; + +function RowList({ children }) { + return ( + <> +

Total rows: {Children.count(children)}

+ ... + + ); +} +``` + +[See more examples above.](#counting-children) + +#### Parameters {/*children-count-parameters*/} + +* `children`: The value of the [`children` prop](/learn/passing-props-to-a-component#passing-jsx-as-children) received by your component. + +#### Returns {/*children-count-returns*/} + +The number of nodes inside these `children`. + +#### Caveats {/*children-count-caveats*/} + +- Empty nodes (`null`, `undefined`, and Booleans), strings, numbers, and [React elements](/apis/react/createElement) count as individual nodes. Arrays don't count as individual nodes, but their children do. **The traversal does not go deeper than React elements:** they don't get rendered, and their children aren't traversed. [Fragments](/apis/react/Fragment) don't get traversed. + +--- + +### `Children.forEach(children, fn, thisArg?)` {/*children-foreach*/} + +Call `Children.forEach(children, fn, thisArg?)` to run some code for each child in the `children` data structure. + +```js RowList.js active +import { Children } from 'react'; + +function SeparatorList({ children }) { + const result = []; + Children.forEach(children, (child, index) => { + result.push(child); + result.push(
); + }); + // ... +``` + +[See more examples above.](#running-some-code-for-each-child) + +#### Parameters {/*children-foreach-parameters*/} + +* `children`: The value of the [`children` prop](/learn/passing-props-to-a-component#passing-jsx-as-children) received by your component. +* `fn`: The function you want to run for each child, similar to the [array `forEach` method](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach) callback. It will be called with the child as the first argument and its index as the second argument. The index starts at `0` and increments on each call. +* **optional** `thisArg`: The [`this` value](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) with which the `fn` function should be called. If omitted, it's `undefined`. + +#### Returns {/*children-foreach-returns*/} + +`Children.forEach` returns `undefined`. + +#### Caveats {/*children-foreach-caveats*/} + +- Empty nodes (`null`, `undefined`, and Booleans), strings, numbers, and [React elements](/apis/react/createElement) count as individual nodes. Arrays don't count as individual nodes, but their children do. **The traversal does not go deeper than React elements:** they don't get rendered, and their children aren't traversed. [Fragments](/apis/react/Fragment) don't get traversed. + +--- + +### `Children.map(children, fn, thisArg?)` {/*children-map*/} + +Call `Children.map(children, fn, thisArg?)` to map or transform each child in the `children` data structure. + +```js RowList.js active +import { Children } from 'react'; + +function RowList({ children }) { + return ( +
+ {Children.map(children, child => +
+ {child} +
+ )} +
+ ); +} +``` + +[See more examples above.](#transforming-children) + +#### Parameters {/*children-map-parameters*/} + +* `children`: The value of the [`children` prop](/learn/passing-props-to-a-component#passing-jsx-as-children) received by your component. +* `fn`: The mapping function, similar to the [array `map` method](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) callback. It will be called with the child as the first argument and its index as the second argument. The index starts at `0` and increments on each call. You need to return a React node from this function. This may be an empty node (`null`, `undefined`, or a Boolean), a string, a number, a React element, or an array of other React nodes. +* **optional** `thisArg`: The [`this` value](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this) with which the `fn` function should be called. If omitted, it's `undefined`. + +#### Returns {/*children-map-returns*/} + +If `children` is `null` or `undefined`, returns the same value. + +Otherwise, returns a flat array consisting of the nodes you've returned from the `fn` function. The returned array will contain all nodes you returned except for `null` and `undefined`. + +#### Caveats {/*children-map-caveats*/} + +- Empty nodes (`null`, `undefined`, and Booleans), strings, numbers, and [React elements](/apis/react/createElement) count as individual nodes. Arrays don't count as individual nodes, but their children do. **The traversal does not go deeper than React elements:** they don't get rendered, and their children aren't traversed. [Fragments](/apis/react/Fragment) don't get traversed. + +- If you return an element or an array of elements with keys from `fn`, **the returned elements' keys will be automatically combined with the key of the corresponding original item from `children`.** When you return multiple elements from `fn` in an array, their keys only need to be unique locally amongst each other. + +--- + +### `Children.only(children)` {/*children-only*/} + + +Call `Children.only(children)` to assert that `children` represent a single React element. + +```js +function Box({ children }) { + const element = Children.only(children); + // ... +``` + +#### Parameters {/*children-only-parameters*/} + +* `children`: The value of the [`children` prop](/learn/passing-props-to-a-component#passing-jsx-as-children) received by your component. + +#### Returns {/*children-only-returns*/} + +If `children` [is a valid element,](/apis/react/isValidElement) returns that element. + +Otherwise, throws an error. + +#### Caveats {/*children-only-caveats*/} + +- This method always **throws if you pass an array (such as the return value of `Children.map`) as `children`.** In other words, it enforces that `children` is a single React element, not that it's an array with a single element. + +--- + +### `Children.toArray(children)` {/*children-toarray*/} + +Call `Children.toArray(children)` to create an array out of the `children` data structure. + +```js ReversedList.js active +import { Children } from 'react'; + +export default function ReversedList({ children }) { + const result = Children.toArray(children); + result.reverse(); + // ... +``` + +#### Parameters {/*children-toarray-parameters*/} + +* `children`: The value of the [`children` prop](/learn/passing-props-to-a-component#passing-jsx-as-children) received by your component. + +#### Returns {/*children-toarray-returns*/} + +Returns a flat array of elements in `children`. + +#### Caveats {/*children-toarray-caveats*/} + +- Empty nodes (`null`, `undefined`, and Booleans) will be omitted in the returned array. **The returned elements' keys will be calculated from the original elements' keys and their level of nesting and position.** This ensures that flattening the array does not introduce changes in behavior. + +--- + +## Troubleshooting {/*troubleshooting*/} + +### I pass a custom component, but the `Children` methods don't show its render result {/*i-pass-a-custom-component-but-the-children-methods-dont-show-its-render-result*/} + +Suppose you pass two children to `RowList` like this: + +```js + +

First item

+ +
+``` + +If you do `Children.count(children)` inside `RowList`, you will get `2`. Even if `MoreRows` renders 10 different items, or if it returns `null`, `Children.count(children)` will still be `2`. From the `RowList`'s perspective, it only "sees" the JSX it has received. It does not "see" the internals of the `MoreRows` component. + +The limitation makes it hard to extract a component. This is why [alternatives](#alternatives) are preferred to using `Children`. diff --git a/beta/src/sidebarReference.json b/beta/src/sidebarReference.json index 24d467463..c7396afdd 100644 --- a/beta/src/sidebarReference.json +++ b/beta/src/sidebarReference.json @@ -13,8 +13,7 @@ "routes": [ { "title": "Children", - "path": "/apis/react/Children", - "wip": true + "path": "/apis/react/Children" }, { "title": "cloneElement",