Children.only is inconsistent with Children.count
See original GitHub issueDo you want to request a feature or report a bug?
Bug
What is the current behavior?
const children = [<div />];
React.Children.count(children);
// => 1
const child = React.Children.only(children);
// => Error('React.Children.only expected to receive a single React element child.')
Repro in CodeSandbox here: https://codesandbox.io/s/1vonwo4807
What is the expected behavior?
It’s excepted that React.Children.only
return the one and only element of the array (and not throw).
Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?
React 16.2.0
I’m not certain if this is the behavior prior to React 16 (pre-Fiber), but Fiber supports and encourages the use of fragments (i.e., arrays of elements); as such, this issue is much more likely to be encountered in React 16 onward.
Furthermore, the above code just reads like something is wrong.
How many children do I have? 1. May I have the only child? No, I expected you to only have one child. Um, okay.
Issue Analytics
- State:
- Created 6 years ago
- Reactions:1
- Comments:6 (5 by maintainers)
Thanks for chiming in.
I’m well aware of how to work around this limitation, but I’m questioning why the limitation exists.
As stated in the docs for
React.Children
:We’re supposed to treat
children
as opaque because the same element hierarchy can take on several representations.For example, the following element hierarchies are the same to React:
The first of these would return
<span>Foo</span>
fromReact.Children.only
, but the latter would not, despite producing the same virtual DOM.This concern is all the more relevant given the new support in React 16 for arrays and fragments wherever an element is expected. I find it especially confusing that these are purported to be the same conceptually, but behave differently as far as
Children.only
is concerned:If the answer is “Look at the type of
this.props.children
and manually unwrap the value,” thenchildren
is no longer opaque, and I might as well ditch theReact.Children
utilities, especially since I’m not sure if they’re going to lie to me.I think the only justification for no-action here is a dogmatic concern for backwards compatibility.
React.Children
utilities are in general not very consistent with each other. They’re also a symptom of lacking primitives features in React — typically solutions usingReact.Children
have other flaws and could be implemented more cleanly if React allowed some way to “call through” components. That’s not to say we’re never willing to change them, but it feels like the backwards compatibility cost is high, and it’s not clear this is worth doing, compared to creating other more idiomatic and powerful ways to address the same use cases.Speaking of this specific issue, it’s naming that’s the problem.
React.Children.only
wasn’t intended to guarantee one child per se, but a safe downcast toReactElement
. The intention was that if you have arbitrarychildren
, you could useReact.Children.only
to be sure you’re dealing with an element (and get a runtime invariant if you’re not). This can be handy in cases where the component doesn’t support dealing with multiple children, and wants to make it explicit early. In that case even passing a single-item array should be a violation because your array might be dynamic, and you might not discover the length limitation until deploying to production. So it’s handy to be able to throw on every array (as well as anything else that’s not an element), and that’s whatReact.Children.only
gives you. PerhapsReact.Children.toElementOrThrow()
would be a more accurate name but I think that ship has sailed, and changing it now isn’t worth the effort.Again, we do want to provide a better story here. But there are more fundamental flaws in
Children
API (e.g. that it can’t “see through” user-defined components, which, unlike theFragment
issue you pointed out, can’t be worked around in userland at all) which seem more worthy to address. We’ll try to avoid the same mistakes in any new APIs though.