Manipulating JSX element's children collection - javascript

I have a React JSX element and I want to iterate through it's children, perform replace (or any other operation) on each string element within it, and return new, modified JSX element. For example:
var element = <span>Text { var1 } Text2 text3 { var2 }</span>;
var modifiedChildren = [];
element.props.children.forEach(function(child){
if(typeof child === 'string') {
var modifiedChild = child.replace('a', 'b');
modifiedChildren.push(modifiedChild);
}
}
var modifiedElement = element;
modifiedElement.props.children = modifiedChildren;
However, element.props.children is read-only, which prevents me from doing this. But that's not what I want either, I just want to make new JSX element with modified children.
What could be the way to achieve this while remaining in ReactJS way of thinking?

You can use React.Children.Map for iterating over the children of a component.
React.Children.map(children, function[(thisArg)])
Some thing like this:
renderChildren() {
return React.Children.map(this.props.children, child => {
React.cloneElement(child, {
newProp: this.props.name
})
})
}
To immutably change the element you can use, React.cloneElement
React.cloneElement(
element,
[props],
[...children]
)
https://reactjs.org/docs/react-api.html#cloneelement
https://reactjs.org/docs/react-api.html#reactchildren
Check this link for more information

Related

How would I assign n number of properties to an object depending on the given parameters in a function?

I am making a more simple way of document.createElement function, and this is what I have so far.
function createEl(type, parent) {
var element = document.createElement(type);
parent.appendChild(element);
}
The problem I have is I don't know how to make it so that I can add use the argumments, and using the parameter, you can assign any number of properties to the element object I have created inside the function. For example, if you wanted to add one that determined element's absolute style. I'm thinking it should have to do Object.assign, but I'm not exactly sure.
You can pass objects, one for attributes and one for style:
function createEl(type, parent, attributes, style) {
var element = document.createElement(type);
//add attributes
Object.entries(attributes).forEach(([attributeName, attributeValue]) => {
element.setAttribute(attributeName, attributeValue);
});
//add style
Object.entries(style).forEach(([styleProp, styleValue]) => {
console.log(styleProp+ styleValue);
element.style[styleProp] = styleValue;
});
domElement = document.getElementById(parent);
domElement.appendChild(element);
}
and call the function with:
const type = "div";
const parent = "test";
const attributes = {"id" : "div1" , "class" : "redButton", "..." : "..."};
const style = {"position" : "absolute", "color" : "green" , "..." : "..."};
createEl(type, parent, attributes, style);
You can set any sort of attriubutes and style properties
if i understynd right, you mean something like
function createEl(type, parent, optional = {}) {
var element = document.createElement(type);
if(optional.sample.lenth)
{
// do something with optional.sample
}
if(optional.foo.lenth)
{
// do something with optional.foo
}
parent.appendChild(element);
}
so you can call it without the third param createEl('div','div#id') or with an object createEl('div','div#id',{param:2,foo:'bar'})

React.Children with non-element children

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.

Setting custom props on a dynamically created React component

I'm refactoring some of my React code for ease of use in places where I can't use Babel directly (such as in short embedded JavaScript on pages). To assist with this I'm setting up a short function that builds the components and passes props to them. This code works just fine:
components.js:
import ResponsiveMenu from './components/responsive-menu';
window.setupMenu = (items, ele) => {
ReactDOM.render(<ResponsiveMenu items={items}/>, ele);
};
static-js.html:
<div id="menu"></div>
<script>
setupMenu({ items: [] }, document.getElementById('menu');
</script>
However, when I attempt to turn it into something more generic to handle more components like so:
components.js:
import ResponsiveMenu from './components/responsive-menu';
import AnotherComp from './components/another-comp';
window.setupComponent = (selector, name, props) => {
let eles;
if (typeof selector == 'string') {
eles = [];
let nl = document.querySelectorAll(selector), node;
for (let i = 0; node = nl[i]; i++) { eles.push(node); }
} else {
eles = $.toArray(selector); // A helper function that converts any value to an array.
}
return eles.map (
(ele) => {
let passProps = typeof props == 'function' ? props(ele) : props;
return ReactDOM.render(React.createElement(name, passProps), ele);
}
);
};
static-js.html:
<div id="menu"></div>
<script>
setupComponent('#menu', 'ResponsiveMenu', { items: [] });
</script>
I then get this error: Warning: Unknown prop "items" on <ResponsiveMenu> tag. Remove this prop from the element. For details, see (really unhelpful shortened link that SO doesn't want me posting)
Please help me understand why this works for the JSX version and not for the more manual version of creating the component.
When you pass string parameter to React.createElement, it will create native DOM element and there is no valid html DOM ResponsiveMenu.
You can store element into hash and store it into window variable.
Example:
// store component into window variable
window.components = {
ResponsiveMenu: ResponsiveMenu
}
//extract component from window variable by name
React.createElement(window.components[name], passProps)

react get all children refs

I have a component that renders dynamic children, each of these children need to have a ref assigned to them e.g. ref={'childID' + index}
once the children have loaded I then need a way to loop over of the children and get their refs.
any way to do this?
You should be able to loop through this.refs object using Object.keys.
Object.keys(this.refs).forEach(key =>
const ref = this.refs[key];
...
);
You can use the callback style for the ref prop to collect all the refs.
Here's a rough example of how this would look.
var refs = {};
function refCollector(id) {
return function(element) {
refs[id] = element;
if(allRefsCollected()) {
// do some work with all refs
}
}
}
function allRefsCollected() {
return Object.keys(refs).length >= children.length;
}
return children.map((Child, index) => {
return <Child ref={refCollector(index)} />;
});
When allRefsCollected returns true, you'll know that all the children have been rendered and refs will be an object, mapping id to element.

How can I pass props/context to dynamic childrens in react?

I am using react, and I am trying to pass props/context to my dynamic childrens,
by dymamic childrens I mean childrens are render using
{this.props.children}
How can I pass to this children (In my code I know it's type) context/props?
In this jsbin there is an example that it dosen't work on dynamic childrens.
http://jsbin.com/puhilabike/1/edit?html,js,output
Though #WiredPrairie's answer is correct, the React.addons.cloneWithProps is deprecated as of React v0.13RC. The updated way to do this is to use React.cloneElement. An example:
renderedChildren = React.Children.map(this.props.children, function (child) {
return React.cloneElement(child, { parentValue: self.props.parentValue });
});
There's not a a great way to do this that is clear and passing all the properties of the parent isn't a great pattern and could lead to some very difficult to follow code if not done carefully (and with excellent documentation). If you have a subset of properties though, it's straightforward:
JsFiddle
Assuming you're using React with Addons, you can clone the children of a React component and set new property values on them. Here, the code just copies a property called parentValue into each child. It needs to create a clone of each element as the child element had already been created.
var Hello = React.createClass({
render: function() {
var self = this;
var renderedChildren = React.Children.map(this.props.children,
function(child) {
// create a copy that includes addtional property values
// as needed
return React.addons.cloneWithProps(child,
{ parentValue: self.props.parentValue } );
});
return (<div>
{ renderedChildren }
</div>)
;
}
});
var SimpleChild = React.createClass({
render: function() {
return <div>Simple { this.props.id }, from parent={ this.props.parentValue }</div>
}
});
React.render((<Hello parentValue="fromParent">
<SimpleChild id="1" />
<SimpleChild id="2" />
</Hello>), document.body);
Produces:
Simple 1, from parent=fromParent
Simple 2, from parent=fromParent
Spreading props on DOM elements
https://github.com/vasanthk/react-bits/blob/master/anti-patterns/07.spreading-props-dom.md
When we spread props we run into the risk of adding unknown HTML
attributes, which is a bad practice.
const Sample = () => (<Spread flag={true} domProps={{className: "content"}}/>);
const Spread = (props) => (<div {...props.domProps}>Test</div>);

Categories

Resources