Get Elements, (children) of React.element - javascript

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

Related

How can I check if element does not exist in a React test?

I want to test that on initial render of the parent component, there are no child components rendered in the document.
On every press of the button, the parent component generates a child component within it. On init, the child component array is empty. I therefore expect my child componet test-id to be null on initial render, when I render my parent component.
Parent component:
const ParentComponent = () => {
const [childComponentsArray, setChildComponentsArray] = useState([]);
const createChildComponent = () => {
const objToAdd = {
// Generate uuid for each component
uuid: uuid()
};
setChildComponentsArray([...childComponentsArray, objToAdd]);
};
return (
<div>
{childComponentsArray.length > 0 && <div>
{childComponentsArray.map(() => {
return <div className={'child-item'}>
<ChildComponent />
</div>;
})}
</div>}
<ButtonContainer variant={'secondary'} label={'+ ' + component.addLabel}
onClick={() => createChildComponent()}
/>
</div>
);
};
Child component:
const ChildComponent = () => {
return (
<div data-testid={'childComponentTestId'}>
<p> I'm in child component! </p>
</div
)
}
Unit test:
test('on render, no child items are visible', () => {
render(
<RecoilRoot>
<ParentComponent />
</RecoilRoot>
)
expect(screen.getByTestId('childComponentTestId')).toBeNull();
});
When executing my test I get the following error in my unit test:
TestingLibraryElementError: Unable to find an element by: [data-testid="childComponentTestId"]
I find this error a bit of a paradox, since that is exactly what I want it to be.
note
Passing the data-testid as a prop does not help.
Using .not.toBeInDocument() does not work.
Using .toBeUndefined() does not work either.
You should use queryByTestIt as it returns null if object is not found.
See more on the documentation site.

Accessing React childs state [duplicate]

This question already has answers here:
Access child state of child from parent component in react
(3 answers)
Closed last year.
I have a React element that renders Child elements with a target state. this target state can change anytime and parent doesn't have access at the moment.
const Parent = () => {
function getTarget(){
//TODO
}
return(
<Button>get target</Button>
{children.map(c=>{
<Child props={props}/>
})}
)
}
const Child = (props) => {
//props stuff
const [target, setTarget] = useState(null)
// this target would be changed by user as they interact.
return(
//child elements
)
}
what I'm trying to do is to get the target state of the Child using button in the Parent with following restraints:
There can be variable amount of Child elements, but only one of them are visible at a time.
The "get target" button has to be in Parent, the "target" state has to be initialized in child, and it's unknown.
because only on Child is active at a time, a solution that works for
return(
<Button>get target</Button>
<Child props={props}/>
)
is also fine.
const Parent = () => {
const [activeTarget, setActiveTarget] = useState(null);
const handleButton = () => {
console.log(activeTarget);
}
return(
<Button onClick={handleButton}>get target</Button>
{children.map(c=>{
<Child setActiveTarget={setActiveTarget} />
})}
)
}
const Child = ({setActiveTarget}) => {
const [target, setTarget] = useState(null);
// when the user interacts call 'setTarget' and 'setActiveTarget' to update both states
// update parent state when child mounts
useEffect(() => {
setActiveTarget(target);
}, [target]} // you can additionally add dependencies to update the parent state conditionally
return(
//child elements
)
}

React functional component - is it possible to pass props to { children }? [duplicate]

This question already has answers here:
How to pass props to {this.props.children}
(32 answers)
Closed 1 year ago.
Workaround at the bottom of the question.
I am trying to pass props down to a child component using {children}.
The Father component:
const FatherComp = ({ children, propsToPassToChild }) => (
<div>Dynamic component content:
{children}
</div>
)
The child component:
const ChildComp = ({ childProps }) => <p>This is the dynamic content</p>
Using them like so:
<FatherComp>
<ChildComp/> // childProps cannot be passed here since it's inside FatherComp
</FatherComp>
I want to pass to the ChildComp the props (propsToPassToChild ) from the FatherComp directly:
const FatherComp = ({ children, propsToPassToChild }) => (
<div>Dynamic component content:
>>>>> {children} <<<<< *passing the props somewhere here*
</div>
)
Is it possible to achive what I am trying to do with react functional components directly and without state management?
Thanks!
************ My solution ***********
So after a little while in a different project I came accross the same question and I found out a solution.
const Father = ({ child }) => {
return (
<div>
{child({ text: 'I am props from father' })}
</div>
);
};
const Child = ({ text }) => {
return (
<div>Hey there {text}</div>
);
};
and render the Father component:
const App = () => <Father child={Child} />
Just pass the child in the props of the father as a function that returns a component.
Altough it's not a 'direct' solution this solved the problem without wrapping the child with the father.
You should use render props
ref: https://reactjs.org/docs/render-props.html#gatsby-focus-wrapper
Yes, you need to do something like this:
<FatherComp>
<ChildComp name="John" age="21"/>
</FatherComp>
const FatherComp = (props) => (
<div>Dynamic component content:
{props.children}
</div>
)
Accept props in the FatherComp and use {props.children} to render all the children components inside of your Father component.

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;

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.

Categories