React.Children with non-element children - javascript

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.

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.

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.

According to the docs:
React.Children.count returns the total number of components in children, equal to the
number of times that a callback passed to map or forEach would be
invoked.
What the above statement means is that if the children element passed to a component is iteratable then only the count will be incremented.
Now lets look at the code snippet that React internally used to calculate count.
React.children.count uses the following code
The major piece of code is
function traverseAllChildren(children, callback, traverseContext) {
if (children == null) {
return 0;
}
return traverseAllChildrenImpl(children, '', callback, traverseContext);
}
So if children is null, it returns 0.
function traverseAllChildrenImpl(
children,
nameSoFar,
callback,
traverseContext,
) {
...
switch (type) {
case 'string':
case 'number':
invokeCallback = true;
break;
case 'object':
switch (children.$$typeof) {
case REACT_ELEMENT_TYPE:
case REACT_PORTAL_TYPE:
invokeCallback = true;
}
}
}
if (invokeCallback) {
// Other code
return 1;
}
So from the above code we infer that we children is String, Number it will return 1;
Moving on to the next part
if (Array.isArray(children)) {
for (let i = 0; i < children.length; i++) {
child = children[i];
nextName = nextNamePrefix + getComponentKey(child, i);
subtreeCount += traverseAllChildrenImpl(
child,
nextName,
callback,
traverseContext,
);
}
}
It implies that for each element within the children array, the count is incremented.
const iteratorFn = getIteratorFn(children);
if (typeof iteratorFn === 'function') {
For functions provided as children which are iteratable such as Maps, Sequence it will iterate though the elements and calculate the subtree count
For an object it will return an error.
invariant(
false,
'Objects are not valid as a React child (found: %s).%s',
childrenString === '[object Object]'
? 'object with keys {' + Object.keys(children).join(', ') + '}'
: childrenString,
addendum,
);
A demo for the above piece of information is in the codesandbox here

TLDR
From the official doc:
React.Children provides utilities for dealing with the this.props.children opaque data structure.
In other words, the React.Children namespace is full of utility functions that are helpful when using this.props.children for its primary intended purpose within the composition model.
The composition model uses the special children property for things that can be rendered directly into the output of a parent component, so utility functions like React.children.count are programmed to only include things that can be directly rendered.
Because a function cannot be directly rendered into the output of a parent component, it is not included in the results from React.Children.count.
What is the children prop?
The children prop is a special prop that React uses to pass a component its children.
As I'm sure you know, JSX is just syntactic sugar for React.createElement.
The call signature for React.createElement looks like this:
React.createElement(type, props, ...children)
So when a component is passed children in JSX like this:
<Foo>
<div>child1</div>
<div>child2</div>
</Foo>
...it results in this code:
React.createElement(Foo, null,
React.createElement("div", null, "child1"),
React.createElement("div", null, "child2")
);
...where the children are passed as the trailing arguments and are available in the component as the special children prop.
What is the children prop for?
The children prop is a special prop used for component composition.
From the Composition vs Inheritance doc:
React has a powerful composition model, and we recommend using composition instead of inheritance to reuse code between components.
...and later:
We recommend that such components use the special children prop to pass children elements directly into their output
So the composition model uses the special children prop to allow for reusable components:
// Renders its children within a container div with class "foo":
const Foo = (props) => (<div className="foo">{props.children}</div>);
What can be passed in the children prop?
Well...anything. There are no actual restrictions on what can be passed in the children prop...
...but only certain things can be passed to a component using children for the composition model and expecting to render the child "directly into their output".
So this is valid:
<Foo>some text</Foo>
...and this is valid:
<Foo><div>something</div></Foo>
...and even this is valid:
<Foo>
<Foo>
some text
</Foo>
</Foo>
...but this is not valid:
<Foo>{() => 'some text'}</Foo>
...since Foo cannot render a function directly into its output.
Why are functions ever passed as children?
As you pointed out, this is the result of the Render Props pattern.
In the doc describing the Render Props pattern it starts with passing a render function as a render prop.
Then it points out that using a prop named render is completely arbitrary and the prop name could be anything...
...and it ends by pointing out that:
we could just as easily use the children prop!
...and showing how a render function could be passed in as the children prop.
But then it includes this warning:
Since this technique is a little unusual, you'll probably want to explicitly state that children should be a function in your propTypes when designing an API like this.
In other words, this is a non-standard use of the special children prop.
Passing something arbitrary as children
As I noted earlier, anything can be passed as children.
So taking it a step further, here is a component that expects an Object containing three functions, header, body, and footer:
const ExpectsObject = (props) => (
<div>
{props.children.header()}
{props.children.body()}
{props.children.footer()}
</div>
);
This highly unusual component would be used like this:
<ExpectsObject>
{{
header: () => (<div>header</div>),
body: () => (<div>body</div>),
footer: () => (<div>footer</div>)
}}
</ExpectsObject>
...and this works just fine.
But again, this is a very non-standard use of the special children property and would require careful documentation, and since this approach does not follow the composition model the utility functions in the React.Children namespace don't know how to handle the custom object in children.
In fact, calling React.Children.count with this particular object as children causes the utility function to throw an error.
Why does React.Children.count not count a function?
So why is a function not included in the count returned by React.Children.count?
React.Children.count is designed to be a utility method for the composition model and associated use of the special children property.
Because a function cannot be rendered directly into the output of a component following the composition model, it is not included in the count returned by React.Children.count.

Related

React.js State updating correctly only once, then failing [duplicate]

This question already has answers here:
Using a Set data structure in React's state
(2 answers)
Closed 1 year ago.
I have a parent and a child component. There are 3 props the parent provides out of which 1 is not updating correctly.
Following is the parent component. The prop in question is selectedFilters (which is an object where keys are mapped to sets) and the relevant update function is filterChanged (this is passed to the child)
import filters from "../../data/filters"; //JSON data
const Block = (props) => {
const [selectedFilters, setSelectedFilters] = useState({versions: new Set(), languages: new Set()});
console.log(selectedFilters);
const filterChanged = useCallback((filter_key, filter_id) => {
setSelectedFilters((sf) => {
const newSFSet = sf[filter_key]; //new Set(sf[filter_key]);
if (newSFSet.has(filter_id)) {
newSFSet.delete(filter_id);
} else {
newSFSet.add(filter_id);
}
const newSF = { ...sf, [filter_key]: new Set(newSFSet) };
return newSF;
});
}, []);
return (
<FilterGroup
filters={filters}
selectedFilters={selectedFilters}
onFilterClick={filterChanged}
></FilterGroup>
);
};
export default Block;
The following is the child component: (Please note that while the Filter component runs the filterChanged function, I think it is irrelevant to the error)
import Filter from "./Filter/Filter";
const FilterGroup = (props) => {
const { filters, selectedFilters, onFilterClick } = props;
console.log(selectedFilters);
const filter_view = (
<Container className={styles.container}>
{Object.keys(filters).map((filter_key) => {
const filter_obj = filters[filter_key];
return (
<Filter
key={filter_obj.id}
filter_key={filter_key}
filter_obj={filter_obj}
selectedFilterSet={selectedFilters[filter_key]}
onFilterClick={onFilterClick}
/>
);
})}
</Container>
);
return filter_view;
};
export default FilterGroup;
When running the application, I find that the selectedFilters updates correctly only once. After that, it only changes temporarily in the main Block.tsx, but eventually goes back to the first updated value. Also, FilterGroup.tsx only receives the first update. After that, it never receives any further updated values.
Here are the logs:
After some experimentation, it is clear that the problem originates from the filterChanged function. But I cannot seem to figure out why the second update is temporary AND does not get passed on to the child.
Any ideas? Thanks in advance.
(If any other info is required, pls do mention it)
I don't think you actually want your filterChanged function to be wrapped with useCallback, especially with an empty deps array. with the empty deps array, I believe useCallback will fire once on initial render, and memoize the result. You may be able to add filter_key and filter_id to the dependency array, but useCallback tends to actually slow simple functions down, instead of adding any real performance benefit, so you may just want to get rid of the useCallback completely and switch filterChanged to a regular arrow function.

Shadow DOM in web components with Vue3

I'm currently testing web components with Vue3 and wondering how this Shadow DOM really works. Some third party library is accessing elements with getElementById() and throwing an error because the element is null.
Apparently that's because there's no access from the web component to the actual DOM. So meaning the functions can't even find the HTML elements used in the components itself. Can anyone explain why that is exactly? And how would I access the elements then? Maybe with shadowRoot?
Test.vue:
<template>
<div id="test">Hello World!</div>
</template>
<script lang="js">
import {
ref,
onMounted
} from "vue";
export default {
setup(props) {
onMounted(() => {
// NULL
console.log(document.getElementById("test"));
});
}
}
</script>
main.js:
import { defineCustomElement } from 'vue'
import Test from './Test.vue'
const ExampleElement = defineCustomElement(Test)
// register
window.customElements.define('test-component', ExampleElement)
Yes, shadowDOM is meant to encapsulate content.
If you do not want that behaviour, then do not use shadowDOM
But if you are using a tool, it might enforce shadowDOM on you,
In that case, ditch the tool and create a Component with Vanilla JavaScript, it ain't rocket science.
If you are learning Web Components it is best to learn the Technology first, and not a Tool... because a Fool with a Tool, is still a Fool.
If the Custom Element (with shadowDOM) exists in the DOM, and it is registered with mode:"open", you can query its contents with:
document.querySelector("test-component").shadowRoot.querySelector("#test")
If you want to find all Web Components in the page, you can do something like this:
// findElements takes a function definition, the output must be Truthy or Falsy
function findElements( accept = x => customElements.get(x.localName) || 0) {
function log() {
console.log(`%c findElements `, `background:purple;color:yellow`, ...arguments);
}
let node, elements = [], shadowRootCount = 0;
function diveNode( diveRoot ) {
// IE9 was last to implement the TreeWalker/NodeIterator API ... in 2011
let iterator = document.createNodeIterator(
diveRoot,
NodeFilter.SHOW_ELEMENT,
node => accept(node) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT
);
while ( node = iterator.nextNode() ) {
if (node.shadowRoot) {
log(`dive into shadowRoot #${++shadowRootCount} at`, node.outerHTML);
[...node.shadowRoot.children].forEach( diveNode );
}
elements.push(node);
}
}
diveNode( document.body ); // initial dive location
log(elements.length, `elements found`,[elements]);
//return elements;
}
findElements((x) => true); // find all DOM elements
findElements(); // find all Custom Elements

An object component

I'm learning React and I'm trying to render a form with a few different form-steps (details, payments, confirm...) from a UI library that has Form component ready, which iterates through those form-steps.
The issue is that each 'step' component looks more like an actual JS object:
export const DetailStep = (
<div>Details screen with inputs, texts and images...</div>
);
It doesn't have the = () => so it's not an actual functional component, so I can't add hooks or functions into it.
That's the array of steps:
const stepPages = [
DetailStep,
PaymentStep,
ConfirmStep
];
And that's the actual Form component that iterates through the array of steps:
<Form
onSubmitClick={onStepSubmit}
render={formRenderProps => {stepPages[step]} }
/>
If I'll change any step to a functional component (DetailStep = () => ...), the step will be blank and won't be rendered.
How can I fix that / just add hooks to each 'step' component?
JSX declarations are converted into React.createElement function calls, this does mean that the stepPages in your example are objects (just that they are react objects)
The magic that turns them into function components happens in the render prop of the form:
<Form
onSubmitClick={onStepSubmit}
render={formRenderProps => {stepPages[step]} }
/>
You can see that it's passing a function in here, that returns the jsx objects. This is kinda the same as putting the jsx directly in the render function.
Although I notice that in your example, the jsx isn't being returned, so I would expect this example to not work correctly.
The longform equivalent would be something like this:
<Form
onSubmitClick={onStepSubmit}
render={formRenderProps => {
if (step === 0) {
return (
<div>Details screen with inputs, texts and images...</div>
)
} else if (step === 1) {
return (
<div>Details screen with inputs, texts and images...</div>
)
} // etc
}}
/>
EDIT to answer question in comments
You're right, so how can I add hooks into those object components?
If each of the form steps needs to use hooks and manage it's internal state, then I would make them functional components instead of JSX objects, then pick the right component from the stepPages map and render it like so:
export const DetailStep = (props) => {
// hooks and stuff here
return (
<div>Details screen with inputs, texts and images...</div>
);
}
const StepComponent = stepPages[step];
return (
<Form
onSubmitClick={onStepSubmit}
render={formRenderProps => <StepComponent {...formRenderProps} />}
/>
);

Change a prop value inside functional component can cause problems in react component?

If I receive a prop and change it in my functional component, can it create a problem? Or it's ok to change it inside the component?
e.g
const MyComponent = ({ foo }) => {
// is this bad?
foo = someCondition ? bar : foo
return (...)
}
I know that I could create another variable and store the values, but I would like to know if changing the prop it self won't cause any problem because it's a functional component.
No, it shouldn't create any problems. As with regular functions the arguments passed are their own variables in the function scope and don't mutate the original value passed to the function.
function something(value) {
value = 'nothing';
}
var anything = 0;
something(anything);
// Anything should still be 0;
console.log(anything);
But I would suggest to not mutate your variables.
If foo in your example is passed from the parrent, and the parrent keeps it in its state, then you would also need to pass setFoo as a paramater to your component and use that to update it properly.
function Parrent(){
let [foo, setFoo] = useState('foo');
return <Child foo={foo} setFoo={setFoo}/>
}
As for changing the props directly, you can if they are arrays or objects.
Props in the React are just read-only variables. You should change the props values by the parent component
I avoid changing the prop.
But I created a simple example and changing the prop in the children do not affected the value in the parent component.
https://codesandbox.io/s/objective-cdn-cq55d
I tested it with several render. Thats why I added an input. Typing in it makes the component rerender.
const MyComponent = ({ foo }) => {
// Not valid
foo = someCondition ? bar : foo
return (...)
}
There are two kinds of data in React,
a)Props(immutable data)
b)State(mutable data)
you are not supposed to change the immutable data(there are some ways to do it, but not recommended). what you should do is, (you can't assign a callback and change from here, i'll explain later why)
if you want to just use just the value inside this component
const baz = foo === condition ? bar : foo
or render something based on foo meets some condition
return (
<div>
{foo === somecondition ? <A /> : <B />}
</div>
)
Or you want to actually change it,
coming from a global state like redux or mobx,
u should change it from the reducers in case of redux or
#action decorated functions in mobx.
if it's a local state which passed down to the child component,
u can set a call back and assign to a click handler in which the case it is feasible
handleClick = () => {
this.setState(prevState => ({
...prevState,
foo: someCondition ? bar : foo,
}))
}
render () {
const { handleClick } = this
return <ChildComponent {...{ handleClick }} />
}
Like said before u can't change the passed down local state from render of the child(or render of any component[actually u can, but may end up in infinite loops: each time a state change happens, the component will re render, so the loop(pure components excluded eg: shouldComponentUpdate() hook which validates an unrelated condition)])
in such cases what u should do is to make the child component also a stateful component and change the parent props with a callback
class Child extends Component {
//set once
componentWillMount() {
if (condition) callback
}
//whenever there is change
componentWillReceiveProps(nextProps) {
if (condition) callback
}
//use correct lifecycle method which meets your requirement..
}

Whats the right way to manipulate a model instance in React?

So I have a React component that accepts an instance of a function constructor (a Car).
The component's job is to display information about the Car and manipulate it based on the Car's public interface (methods and properties).
In the example below, a child component should add an accident on button click.
Question: What is the right way for the child to manipulate properties of the Car instance? The root parent's state stores reference to the instance of the Car, and the children are able to manipulate the Car's properties (like .accidents), but see the various onChange examples for why I'm struggling to find the right React way to do this.
I'd like to avoid a heavy handed solution like Flux to store this state.
Any suggestions would be appreciated!
function Car(name, color) {
this.name = name;
this.color = color;
this.accidents = [];
}
const myCar = new Car('Ferrari', 'Red');
myCar.accidents.push('accident #1');
class Accident extends React.Component {
handleButton1 = () => {
const newAccident = 'accident type1 # ' + Math.floor(Math.random()*100);
this.props.onChange1(newAccident);
}
handleButton2 = () => {
const newAccident = 'accident type2 # ' + Math.floor(Math.random()*100);
this.props.onChange2(newAccident);
}
handleButton3 = () => {
const newAccident = 'accident type3 # ' + Math.floor(Math.random()*100);
this.props.accidents.push(newAccident);
this.props.onChange3();
}
handleButton4 = () => {
const newAccident = 'accident type4 # ' + Math.floor(Math.random()*100);
this.props.accidents.push(newAccident);
// This circumvents React's state management, so the parent doesnt
// rerender when its state changes.
}
render() {
return (
<div>
<button onClick={this.handleButton1}>
Add accident (onChange1)
</button>
<button onClick={this.handleButton2}>
Add accident (onChange2)
</button>
<button onClick={this.handleButton3}>
Add accident (onChange3)
</button>
<button onClick={this.handleButton4}>
Add accident (option 4)
</button>
<ul>
{this.props.accidents.map((a, i) => <li key={i}>{a}</li>)}
</ul>
</div>
)
}
}
class DisplayCard extends React.Component {
state = {
editingCar: this.props.car
}
// Push the new accident into state and set it with the same reference.
onChange1 = (newAccident) => {
this.state.editingCar.accidents.push(newAccident);
// Is this semantically different than calling this.forceUpdate?
this.setState({
editingCar: this.state.editingCar,
});
}
// Clone the existing state we want to update and explicitly set that new state
onChange2 = (newAccident) => {
const newAccidentList = _.cloneDeep(this.state.editingCar.accidents);
newAccidentList.push(newAccident);
// Setting our new accident list like this converts editingCar to a POJO
// editingCar.name is lost because a deep merge does not happen.
this.setState({
editingCar: {
accidents: newAccidentList
},
});
}
// Just force update - this.state.editingCar was manipulated by <Accident />.
onChange3 = () => {
this.forceUpdate();
}
render() {
return (
<div>
<div>Car Name: {this.state.editingCar.name}</div>
<Accident
accidents={this.state.editingCar.accidents}
onChange1={this.onChange1}
onChange2={this.onChange2}
onChange3={this.onChange3}
/>
</div>
);
}
}
ReactDOM.render(
<DisplayCard car={ myCar } />,
document.getElementById('container')
);
Also on JSFiddle if you want to play around: https://jsfiddle.net/jamis0n003/fbkn5xdy/4/
EDIT: The React JS docs suggest integrating with "other libraries", such as Backbone models, using forceUpdate:
https://reactjs.org/docs/integrating-with-other-libraries.html#using-backbone-models-in-react-components
When state is stored in a parent component and a child component wants to manipulate that state, the parent should pass a callback function to the child's props. Then the child calls the callback to notify the parent to modify its own state. The child should never modify props since the change can have unintended consequences due to the way objects are referenced in JavaScript.
If you want to get really fancy, you can use Redux which stores "global" state in the top-most parent component. All child components issue (or dispatch) actions which notify the top-level parent to update its state which is then passed down again to all children components through their props.
What is the right way for the child to manipulate properties of the Car instance?
In general, rely on setState() to update state, which will reliably redraw the view, or if you mutate the data use forceRedraw() to ensure the view is redrawn with the latest data -- but using setState() is much preferred. In either case a child must notify a parent of a change using a callback like you have, but instead of having the child Accident actually change the data, make it a "dumb" component which notifies the parent of an intended change and the parent actually makes the change.
I'd like to avoid a heavy handed solution like Flux to store this state.
You may want to look into MobX, which is popular alternative to Flux/Redux that is a bit easier to get into because it allows you to mutate objects very much in the way you are already doing.

Categories

Resources