Do 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._
If the children has only one item, it would be ReactElement, not ReactElement[].
@meowtec is right. If you modify your CodeSandbox example from [<div>Didn't throw</div>] to <div>Didn't throw</div> it will work as expected. The inconsistency I see that is confusing, in this case, is that count works in both cases. And this is by design. Check the docs for React.Children.only for more information.
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:
React.Children provides utilities for dealing with the this.props.children opaque data structure.
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:
const element = (
<div>
<span>Foo</span>
</div>
);
const element = (
<div>
{
[<span>Foo</span>]
}
</div>
);
The first of these would return <span>Foo</span> from React.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:
const element = [
<span>Foo</span>
];
const element = (
<Fragment>
<span>Foo</span>
</Fragment>
);
If the answer is "Look at the type of this.props.children and manually unwrap the value," then children is no longer opaque, and I might as well ditch the React.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.
@jamesreggio sounds reasonable to me. Either React.Children.only takes array, or Fragment not return array in this case. Either way solves confusion I think.
Any maintainers care to chime in?
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 using React.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 to ReactElement. The intention was that if you have arbitrary children, you could use React.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 what React.Children.only gives you. Perhaps React.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 the Fragment 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.
Most helpful comment
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
childrenas 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.onlyis concerned:If the answer is "Look at the type of
this.props.childrenand manually unwrap the value," thenchildrenis no longer opaque, and I might as well ditch theReact.Childrenutilities, 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.