React: Mapping children of a parent component - javascript

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.

Related

How to pass props to children according to type in React + Typescript

Apologies if this has been answered elsewhere, it seems like a basic problem but I wasn't able to find an answer anywhere.
I am trying to find a way to conditionally pass props to children components based on the the type (i.e. component type) of the child.
For instance given a generic functional component
const Parent = ({propA, propB, children, ...props}) => (
<div {...props}>
{children}
</div>
)
in which I expect to receive only A and B components as children. I want propA to be passed only to children of type A and propB to be passed only to children of type B such that
const Parent = ({propA, propB, children, ...props}) => (
<div {...props}>
{React.Children.map<React.ReactNode, React.ReactNode>(children, child => {
if (React.isValidElement(child)) {
if (/* child if of type A */)
return React.cloneElement(child, { propA })
if (/* child if of type B */)
return React.cloneElement(child, { propB })
}
})}
</div>
You can use child.type
const Parent = ({propA, propB, children, ...props}) => (
<div {...props}>
{React.Children.map<React.ReactNode, React.ReactNode>(children, child => {
if (React.isValidElement(child)) {
if (child.type===A) {
return React.cloneElement(child, { propA })
} else {
return React.cloneElement(child, { propB })
}
}
})}
</div>)
if you know the mapping between Component and its respective props, you can create an array of objects and then map over it, while passing it from the parent.
Example
//calling of parent
<Parent renderObject = {[
{prop : propA, comp: compA},
{prop : propB, comp: compB}
]}
...restOfTheProps
/>
Now you can actually code something like this using map
const Parent = ({ renderObject, ...props}) => (
<div {...props}>
{renderObject.map((renderObj)=>{
return <renderObj.comp {...renderObj.prop} />
})}
</div>
)
instead of using children, you can pass component as props.
same this you can do it!
<Modal isActive={true} >
<h1>Modal here!</h1>
</Modal>
and in Your component do same this:
type Props ={
children: React.ReactNode;
isActive: boolean;
}
const Modal:React.FC<Props> = ({children, isActive}) => {
return isActive && children;
}
export default Modal;

Observer does not work for recursive items

I'm really stuck with the problem. I have a nested menu. After click on the first menu level second level items shows up. After click on one of the children nothing happens. Looks like the observer don't work for deep levels items. I don't know if the "late" type could be a problem. Everything is described in the code:
https://codesandbox.io/s/mobx-state-tree-recursive-deep-menu-p7eqj
Thanks in advance
The problem is that when you are using your recursive TreeItem component it is not actually observer. You wrapping it in observer only when exporting, but TreeItem inside TreeItem is regular component, not observer so it doesn't react to anything.
Basically you just need to move observer decorator:
// Use `observer` right here straight away
const TreeItem = observer((props) => {
const handleClick = () => {
props.item.active ? props.item.deactivate() : props.item.activate();
};
const ItemChildren = ({ children }) => {
return (
<ul>
{children.map((child) => (
<TreeItem key={child.id} item={child} />
))}
</ul>
);
};
return (
<React.Fragment>
<li onClick={handleClick}>{props.item.id}</li>
{props.item.active && (
<ItemChildren children={values(props.item.children)} />
)}
</React.Fragment>
);
});
// Remove it from here
export default TreeItem;
Codesandbox

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>
);
};

How can I pass a prop by child index in React

I'm trying to develop a generic container for React, that would work like this:
<PanelContainer>
<PanelConsole />
<PanelMemory />
<PanelLog />
</PanelContainer>
I want to dynamically create a tab system within the container, this works as follows:
renderTabs = () => {
return (
<ul className="panel_tabs">
{React.Children.map(this.props.children, (child, i) =>
<li key={child.type.display_name} onClick={() => this.handleClickTab(i)}>
{child.type.display_name}
</li>
)}
</ul>
);
}
This allows me to render the tabs with the display_name property within the class. This so far works, but now I'm trying to get the click to work. I want it to work dynamically so I don't have to build specialized containers for each instance of the panel. I'd ideally like to set the property of a child in this.props.children by index, so for example:
this.props.children[0].props.shown = false;
Is this possible?
I think React.Children.map and React.cloneElement works for you:
render() {
const { children } = this.props;
const tabs = this._renderTabs();
const childrenWithProps = React.Children.map(children, (child, id) =>
React.cloneElement(child, { shown: this.state.shows[i] }));
return (
<div>
<div>{tabs}</div>
<div>{childrenWithProps}</div>
</div>
)
}

Passing props to generic children

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>
);
}
});

Categories