From a7faa14659b118405dc13d243ce22e7b93aebedb Mon Sep 17 00:00:00 2001 From: Artemy Tregubenko Date: Thu, 20 Sep 2018 12:35:04 +0200 Subject: [PATCH] React.Children.toFlatArray --- text/0000-children-toflatarray.md | 155 ++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 text/0000-children-toflatarray.md diff --git a/text/0000-children-toflatarray.md b/text/0000-children-toflatarray.md new file mode 100644 index 00000000..5669cafb --- /dev/null +++ b/text/0000-children-toflatarray.md @@ -0,0 +1,155 @@ +- Start Date: 2018-09-20 +- RFC PR: +- React Issue: + +# Summary + +A new API `React.Children.toFlatArray` will traverse not only the direct children +but will also go into `Fragments` and recursively traverse their children as well +so that the resulting array does not contain any `Fragments`. + +# Basic example + +```jsx harmony +import React, { Fragment, Children } from 'react' + +function TabsContainer({ children }) { + console.log(Children.toFlatArray(children)) // [, , ] instead of [, ] +} + +function App() { + return ( + + + + + + + + ) +} +``` + +# Motivation + +There has been a need to operate on sets of sibling elements without a wrapper DOM element. +This need has been addressed by introducing `` which allows passing around +lists of elements. + +The original documentation for `Fragments` also stated that they will be transparent +for the APIs in the `React.Children` namespace, i.e.: + +> If children is a keyed fragment or array it will be traversed: +> the function will never be passed the container objects. + +This design is arguably convenient: there are likely only a few cases where the developer +of the component would like to differentiate between receiving a `Fragment` as a child +and receiving children of the `Fragment` directly. + +Contrary to this documentation the APIs in the `React.Children` namespace do not traverse +children of the `Fragments`. There have been reports about this ( +[1](https://github.com/facebook/react/issues/11859) +[2](https://github.com/facebook/react/issues/12662) +[3](https://github.com/facebook/react/issues/13677) +) but apparently React developers consider this an intentional behavior. + +At the moment there is no elegant solution to traverse the children of an element +while treating `Fragments` as transparent containers. An example use case for this would be +a tabs container inspecting its children tabs where the list of tabs is dynamic +and several of them are wrapped in a conditionally present `Fragment`. +Arguably every such pair of container and element components will require a way to access +the direct descendants of the container, wrapped in `Fragment` or not. + +One way to solve this would be to change the existing behaviour +of the APIs in the `React.Children` namespace to reflect the original documentation. +This solution has downsides of being a potentially breaking change of existing APIs +and of preventing further evolution of `Fragments`. Its upsides are smaller API surface +and fewer surprises for developers who use i.e. `React.Children.count` and suddenly break it +by wrapping the children in a `Fragment`. + +The proposed solution is to lean on existing API `React.Children.toArray` +and to provide a similar API `React.Children.toFlatArray`. +The latter would treat the `Fragments` as transparent containers +and would not include them in the output while including their children. +Other APIs could be left intact since all of them could be easily re-implemented for this array. +The upsides are the absence of breaking changes and the potential to extend `Fragments` +with new features. The downsides are larger API surface and potentially surprising behaviour +of the rest of the APIs in the `React.Children` namespace. + +# Detailed design + +A new API `React.Children.toFlatArray` is added to convert +the `this.props.children` opaque data structure to a flat array of elements +with keys assigned to each child. + +The difference with `React.Children.toArray` is that if any of the elements +in `this.props.children` is a `Fragment` then this `Fragment` is not added +to the resulting array, instead its direct children are added. +If this `Fragment` contains another `Fragment` then the inner `Fragment` +is expanded in the same way recursively. + +An example use case: + +```jsx harmony +import React, { Fragment, Children } from 'react' + +function TabsContainer({ children }) { + const tabs = Children.toFlatArray(children) + const titles = tabs.map(tab => tab.props.title) + return ( + + {titles.join(', ')} + {children} + + ) +} + +function SettingsForm(props) { + const { basic } = props + return ( + + + {basic || ( + + + + + )} + + ) +} +``` + +# Drawbacks + +- larger API surface due to the new API +- the rest of the APIs in the `React.Children` namespace keep their potentially surprising behaviour + +# Alternatives + +1) Instead of creating a new API a new parameter can be added to the `React.Children.toArray`. + This is less visible in the code and less explicit compared to the `React.Children.toFlatArray`. +2) Existing behaviour of the APIs in the `React.Children` namespace could be changed + to treat `Fragments` as transparent containers. This is a potentially breaking change which also + hampers the potential further development of `Fragments`. +3) A separate small npm package could handle this task. This will be much less visible to developers, + and it will have to transform `keys` once again. + +# Adoption strategy + +On discovering problematic behaviour of existing APIs developers will refer to React’s documentation +and will find the solution there. The same scenario applies to the developers of other +React-based libraries. + +# How we teach this + +The term `flat` is universally accepted for this purpose across frameworks and programming languages. +No special explanation or presentation is necessary beside saying that both arrays and `Fragments` +are flattened. New API does not change how React is taught. + +The documentation of the other APIs in the `React.Children` namespace will have to me modified +to hint that flattening `Fragments` requires the use of this new API. The documentation on `Fragment` +might benefit from hinting at this as well. + +The existing React developers will refer to React’s documentation on discovering +the “problematic” APIs behaviour and will find the solution in the documentation.