Passing props to generic children - javascript

Is there way to pass props to a generic child (not a component that you know ahead)?
Something that would make Wrapper be able to pass foo to children.
var Wrapper = React.createClass({
render: function () {
return <div>
{this.props.children foo={2}}
</div>
}
});
var App = React.createClass({
render: function () {
return (
<Wrapper>
{this.props.foo}
</Wrapper>
)
}
});
jsfiddle

Imagine Javascript code
this.props.children foo=2
this is what your expression is transpiled into from JSX into plain JS. The fact is, you can't pass props to children directly because children isn't a React component. To make it work, you need to map through children and pass your props per every item of iterable.
The problem that comes next is that you can't simply do
this.props.children.map(child => (
<Child foo={2} />
))
because, first, you'll receive TypeError because map is undefined, and second, you'd lose all initial props of every child.
You'll need to use React.Children.map static function as well as React.cloneElement to make it work:
React.Children.map(children, child => React.cloneElement(child, {
foo: 2
}))
This way, every child element preserves its own props passed from parent element and, in addition to them, receive new props you define. Be careful with it because you may unintentionally redefine values of some props, too.
Your example code will then look like
var Wrapper = React.createClass({
render: function () {
const {
foo
} = this.props;
return (
<div>
{React.Children.map(this.props.children, child => React.cloneElement(child, {
foo
}))}
</div>
);
}
});
var App = React.createClass({
render: function () {
return (
<Wrapper foo={2}>
<div>I should be a component</div>
<div>I should be a component, too</div>
<div>We all should be components</div>
</Wrapper>
);
}
});

Related

Can't pass useState() 'set' function to grand child

I'm having issues trying to get my useState variable to work. I create the state in my grandparent then pass it into my parent. Here's a simplified version of my code:
export function Grandparent(){
return(
<div>
const [selectedID, setSelectedID] = useState("0")
<Parent setSelectedID2={setSelectedID} .../> //(elipses just mean that I'm passing other params too)
<div />
)}
Parent:
const Parent = ({setSelectedID2 ...}) => {
return(
<div>
{setSelectedID2("5")} //works
<Child setSelectedID3={setSelectedID2} />
</div>
)
}
From the parent I can use 'setSelectedID2' like a function and can change the state. However, when I try to use it in the child component below I get an error stating 'setSelectedID3' is not a function. I'm pretty new to react so I'm not sure if I'm completely missing something. Why can I use the 'set' function in parent but not child when they're getting passed the same way?
Child:
const Child = ({setSelectedID3 ...}) => {
return(
<div >
{setSelectedID3("10")} //results in error
</div>
);
};
In React you make your calculations within the components/functions (it's the js part) and then what you return from them is JSX (it's the html part).
export function Grandparent(){
const [selectedID, setSelectedID] = useState("0");
return(
<div>
<Parent setSelectedID2={setSelectedID} .../> //(elipses just mean that I'm passing other params too)
<div />
)}
You can also use (but not define!) some js variables in JSX, as long as they are "renderable" by JSX (they are not Objects - look for React console warnings).
That's your React.101 :)
Here's a working example with everything you have listed here. Props are passed and the function is called in each.
You don't need to name your props 1,2,3.., they are scoped to the function so it's fine if they are the same.
I moved useState and function calls above the return statement, because that's where that logic should go in a component. The jsx is only used for logic dealing with your display/output.
https://codesandbox.io/s/stupefied-tree-uiqw5?file=/src/App.js
Also, I created a working example with a onClick since that's what you will be doing.
https://codesandbox.io/s/compassionate-violet-dt897?file=/src/App.js
import React, { useState } from "react";
export default function App() {
return <Grandparent />;
}
const Grandparent = () => {
const [selectedID, setSelectedID] = useState("0");
return (
<div>
{selectedID}
<Parent setSelectedID={setSelectedID} selectedID={selectedID} />
</div>
);
};
const Parent = ({ selectedID, setSelectedID }) => {
setSelectedID("5");
return (
<div>
{selectedID}
<Child setSelectedID={setSelectedID} selectedID={selectedID} />
</div>
);
};
const Child = ({ selectedID, setSelectedID }) => {
setSelectedID("10");
return <div>{selectedID}</div>;
};
output
10
10
10
const [selectedID, setSelectedID] = useState("0")
should be outside return

Get Elements, (children) of React.element

So I'm having an issue I would like to resolve, Maybe someone has an answer for it.
My problem is that I have a Component that has its own Views and Components, at the same time I have a Parent Component thats using the this specific Component.
I want to check if the child of the Child Component has some props.
Child Component
const Child = () => {
return (
<View wantedArgument={true}>
<View anotherWantedArgument={false}>
</View>
</View>
)
}
Parent Component
const Parent = () => {
return (
<Child>
</Child>
)
}
So I want to get the props values of the child views.
I can use useRef for those Views, but it's not that generic and dynamic.
My question is, is there a way I can get those elements of the child?
Thanks ahead
Eden.
You can check props of Parent's children using React.Children API.
In this example, we checking the props of every Parent's child and logging them.
If you want to go to a deeper level (Child of Child of Child etc.), do it with recursion with inductive step child.props.children (if available).
const Child = ({ prop }) => {
return <>{prop}</>;
};
const Parent = ({ children }) => {
useEffect(() => {
React.Children.forEach(children, child => {
console.log(child.props);
});
}, [children]);
return <div>{children}</div>;
};
const App = () => {
return (
<Parent>
<Child prop={1} />
</Parent>
);
};

Can I access function's children inside from parent via React.Children.toArray?

Can I access function's children inside from parent via React.Children.toArray?
Here is a Parent function that counts children. In a real world example, it should have done something with the children.
function Parent({ children }) {
// Flat children.
function flatChildren(arr, initial) {
return arr.reduce((accumulator, current) => {
if (React.isValidElement(current) && current.props.children) {
return flatChildren(React.Children.toArray(current.props.children), [
...accumulator,
current
]);
}
return [...accumulator, current];
}, initial || []);
}
const flat = flatChildren(React.Children.toArray(children));
return <div>Length: {flat.length}</div>;
}
And here is a Child function:
function Child() {
return (
<div>
<span>test</span>
</div>
);
}
Now if I render the Parent with the function or with plain React Elements, it does not work the same:
<>
<Parent>
<div>
<span>test</span>
</div>
</Parent>
<Parent>
<Child />
</Parent>
</>
The first displays "Length: 3" and the second displays "Length: 1".
I can understand why: React did not execute the function: it's a black box. Still, the component is rendered, so there must be a way to map the actual rendered children. Is it possible?
Side note: I actually call useEffet in my Parent for parsing children, so maybe I can do something here.

React: Mapping children of a parent component

So I want to add certain styles to any child that's appended to a component. Let's say the parent component is called Section and children are called Cardin this case. in Section.js I am trying this: -
renderChildren = () =>{
return React.Children.map(this.props.children, (child, i)=>{
let el = React.cloneElement(child,{
style: {opacity:0.5}
})
return el
})
}
render(){
<ScrollView>
{this.renderChildren()}
</ScrollView>
}
The above approach doesn't work for me. And I would like to know why. Also is there a way where I could map across the children and wrap them in a new component? Something like this;
this.props.children.map(Child => <Wrapper> <Child/> </Wrapper> )
To wrap your children into a wrapper just put the call to React.Children.map into the wrapper component:
const OpaqueWrapper = ({ children }) => (
// check that children is defined
// if you do not want your wrapper to be rendered empty
children && (
<Wrapper>
{React.Children.map(children, child => (
React.cloneElement(child, {style: {...child.props.style, opacity: 0.5}})
))}
</Wrapper>
)
);
Also note that you have to merge the styles provided to the original child with the styles injected or you will lose the ability to style the children at all.
See this codesandbox for a working example.
As to why it did not work in your code: Are you sure that your <Card> component does handle the style prop correctly, i.e. applying it to it's children?
EDIT:
The sloution wraps all children components in a single wrapper, but I
would like to wrap each child with the applied wrapper , as shown in
my question.
The just move the wrapper into React.Children.map:
const OpaqueWrapper = ({ children }) => (
React.Children.map(children, child => (
<Wrapper>
{React.cloneElement(child, {style: {...child.props.style, opacity: 0.5}})}
</Wrapper>
)))
);
I think this solution is the simplest for wrap every child. When the children are rendered, you receive an instance of the component, not the component function. And you just need to wrap the instance into the wrapper component as shown below.
this.props.children.map(child => <Wrapper>{child}</Wrapper> )
For TypeScript:
React.Children.map(props.children, child => {
return <Wrapper>{child}</Wrapper>
})
And here the Typescript version when you write properties:
const mapped = Children.map(children, (child, index) => {
if(React.isValidElement(child)) {
return React.cloneElement(child, {
...child.props,
isFirst: index === 0,
isLast: !Array.isArray(children) || index === children.length - 1,
})
}
return null
})
Another variant for TypeScript which I think is clean:
const ChildrenWithProps = Children.map(children, child =>
cloneElement(child as JSX.Element, props),
)
used like:
return (
<div>
{ChildrenWithProps}
</div>
);
Of course, you need to know beforehand that what is passed as children definitely will be a valid child element, or you need to check it with isValidElement as previous answers suggested.

react passing function to child not working

I have a react component that passes a function to a child component, and I bind it to onClick. But when I click I get an error:
this.props.beBad is not a function, this.props.beBad is not defined
it looks something like this:
var Dad = React.createClass({
beBad: function(someInput) {
alert('being bad ' + someInput);
},
render: function() {
var Children = this.state.children.map(function(data, index) {
return (
<Child beBad={this.beBad} key={index}/>
);
});
return (
<div>
{Children}
</div>
);
});
var Child = React.createClass({
beBad: function() {
this.props.beBad('some input');
},
render: function() {
return(
<div onClick={this.beBad}>
be bad
</div>
);
}
});
It doesn't look like this is what you expect. When using React.createClass, React will autobind the correct this IFF the call is at the top level of the function, so in this case it will autobind correctly:
render: function() {
return (
<div onClick={this.beBad}> // autobound
be bad
</div>
);
}
But not here:
var Children = this.state.children.map(function(data, index) {
return (
<Child beBad={this.beBad} key={index}/> // `this` is not what you expect
);
});
return (
<div>
{Children}
</div>
);
The reason is because map is creating a new scope, and this is no longer what you'd expect, and React can't autobind it for you. You need to pass this around explicitly:
var Children = this.state.children.map(function(data, index) {
return (
<Child beBad={this.beBad.bind(this)} key={index}/> // bind `this`
);
}, this); // pass `this`
Note: Seeing the other response, it is possible that React autobinds it for you inside the map, but in general I wouldn't rely on autobinding, explicit wins over implicit. Plus if you ever write React with ES6 classes it doesn't do autobinding.
You've got a problem with your this because you're on the "map scope".
Try to pass this to your map :
var Children = this.state.children.map(function(data, index) {
return (
<Child beBad={this.beBad} key={index}/>
);
}, this);
For your information see : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map

Categories