Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React.Children with non-element children

Tags:

Given a component receives a function as a child (callback as a child pattern, also known as render prop pattern):

<Foo>{() => <Bar/>}</Foo> 

React.Children.count(props.children) === 0 in Foo.

The documentation doesn't seem to mention that React.Children accepts only valid React elements, so the fact that child function is ignored looks odd.

How does React.Children treat non-element children and why?

References to official sources and/or source code are welcome.

like image 255
Estus Flask Avatar asked Sep 28 '18 10:09

Estus Flask


People also ask

Can React children be a function?

React Child FunctionReact allows for you to specify a function as a child, which children is just a normal prop so it is equivalent to a render callback.

Can React components have children?

React Components and Children In React, a component can have one, many, or no children.

How do you fix objects are not valid as a React child?

The React. js error "Objects are not valid as a React child" occurs when we try to directly render an object or an array in our JSX code. To solve the error, use the map() method to render arrays or access properties on the object in your JSX code.


2 Answers

As others have stated, the documentation states that React.Children.count(children) only returns the count of the number of children that are valid React Components.

React.Children does not ignore other types of children, and if you need to get the count, you only need to determine the length of the array in the root child Object, just like you would in vanilla js. If you look at react-motion, you'll see that they specify that children must be type of func:

Mouse.propTypes = {   children: PropTypes.func.isRequired }; 

And they further ensure that there's only one child with React.Children.only (docs):

render(): ReactElement {     const renderedChildren = this.props.children(this.state.currentStyle);     return renderedChildren && React.Children.only(renderedChildren);   } 

React does not handle different types of children on its own, instead, you have to handle them yourself. I put together a code sandbox to show you why.

like image 159
Cher Scarlett Avatar answered Nov 01 '22 08:11

Cher Scarlett


Update:

Disclaimer: It's not a solution but just an eye where we could look at.

I'm not sure but if it is indeed needed to be fixed in React itself, then I would suggest to change in the following function:

React Element

export function isValidElement(object) {   return (     typeof object === 'object' &&     object !== null &&     object.$$typeof === REACT_ELEMENT_TYPE   ); } 

This code:

typeof object === 'object' 

To something like:

typeof Object.create(object) === 'object' 

And also add a Symbol for such something like:

 Symbol.for('react.function') 

in the React Symbol.

Current solution to count those children with:

this.props.children.length 

This lets you count the function as child component as a children. this.props.children includes any type of element, expressions, or component whilst this.props.children inside React.Children function as child is being ignored as children. Continue reading bellow to understand it better...

Here's a demo.


React doesn't consider function as child component as CHILDREN.

However, I have just submitted an issue and if you wish you can keep following there.

The docs specifies that the React.Children.count only counts the component in children.

You probably already have known what exactly is children in react.

React takes everything as children except the function as child.

Here's the reference where it states:

React components don't support functions as children.

If you wish you can look deeper here.

So, you have function as a child in <Foo /> component so it does return 0 as it's not being considered as children.

You can optionally count those expressions as its children then you may convert them to array first and then count like:

class CountExpression extends React.Component {   render() {     const children = React.Children.toArray(this.props.children)     return <p>{React.Children.count(children)}</p>   } }  { /* Counts 2 */ } <CountExpression>   {'one'}   {'two'}   { () => <p>Still, this will be ignored as child and is not included in array</p>} </CountExpression> 

Here's a draft demo if you want to have a look.

More on using children...

Look at the following example how children is being counted:

class CountChildren extends React.Component {   render() {     return <p>{React.Children.count(this.props.children)}</p>   } }  { /* Renders 1 */ } <CountChildren>   Simply a text! </CountChildren>  { /* Renders 2 */ } <CountChildren>   <p>Html element</p>   <ChildComponent /> </CountChildren>  { /* Renders 3 */ } <CountChildren>   Simply a text!   <p>Html element</p>   <ChildComponent /> </CountChildren>  { /* Renders 3 */ } <CountChildren>   Simply a text!   <p>Html element</p>   <ChildComponent />   { /* ignores it as it's not a component */ }   { () => <div>Function as a child component</div> } </CountChildren> 

So, you can notice that React can accept any type of children regardless of array, a function, or an object, etc.

If you wish you can also ignore rendering the children checking it with a condition. For eg.:

class SingleChildComponent extends React.Component {   render() {     return (       <div>         {           Array.isArray(this.props.children) ?             'Sorry, you can only pass single child!' :             this.props.children()         }        </div>     )   } }  { /* Renders 'Sorry, you can only pass single child!' */ } <SingleChildComponent>   <p>First children</p>   <SecondChildren /> </SingleChildComponent>  { /* Renders `<p>Single child</p>` */ } <SingleChildComponent>   <p>Single child</p> </SingleChildComponent> 

If you wish, you can convert the children to array and sort it out like below:

class SortComponent extends React.Component {   render() {     const children = React.Children.toArray(this.props.children)     return <>{ children.sort().join(', ') }</>   } }  { /* Renders 'Computer, Furniture, Machine' */ } <SortComponent>   {'Machine'} { /* First child */ }   {'Computer'} { /* Second child */ }   {'Furniture'} { /* Third child */ } </SortComponent> 

Enforcing a single child:

Bad:

class OnlyChildComponent extends React.Component {   render() {     return this.props.children()   } }  { /* Enforcing it as a single child component */ } OnlyChildComponent.propTypes = {   children: React.PropTypes.func.isRequired } 

If there are more children, then it just shows warning in the console and let the program execute next.

Good:

class OnlyChildComponent extends React.Component {   render() {     return React.Children.only(this.props.children)()   } } 

If there are more than one child, then it will throw an error! And it halts the program execution. It's perfect to avoid mess with our component.

like image 44
Bhojendra Rauniyar Avatar answered Nov 01 '22 08:11

Bhojendra Rauniyar