The problem is a couple of things. I started by changing children: Array<any> to children: React.ReactNode. You already have a check in there to narrow the type from ReactNode to ReactElement. The trick was 1. using the generic type arguments in isValidElement and 2. using a new variable with a type assignment on it elementChild rather than dealing with and mutating the child argument. EnrichedChildren may need to be updated to match your use case.
interface EnrichedChildren {
onChange(): void
selectedValue: string
name: string
children?: React.ReactNode
}
enrichRadioElements = (children: React.ReactNode, name: string): any =>
React.Children.map(children, child => {
if (!React.isValidElement<EnrichedChildren>(child)) {
return child
}
let elementChild: React.ReactElement<EnrichedChildren> = child
if (child.props.children) {
elementChild = React.cloneElement<EnrichedChildren>(elementChild, {
children: this.enrichRadioElements(elementChild.props.children, name),
})
}
if (elementChild.type === 'Radio') {
return React.cloneElement(elementChild, {
onChange: () => {},
selectedValue: 'value',
name: name,
})
} else {
return elementChild
}
})
Answer from Donovan Hiland on Stack OverflowChildren API is marked as "legacy". What's the .map() alternative?
Type issues when mapping `React.Children.map` and `RX.Component`
Accessing React.children props
reactjs - React.Children.map Typescript - Stack Overflow
I'm browsing the latest React (beta) docs, and the Children API is marked as legacy. They have examples for alternatives (which I'm attempting to implement), but aren't showing how to replace the Children.map() function when each child is an unknown component.
Consider a form, where I'm using many different components (<Input />, <Checkbox />, <Button />, etc). I've replaced the cloneElement() function here by returning a new object in the loop with newChild():
const Widget = ({children, name}) => {
const newChild = (child,i) => {
const {props} = child
return {...child, props: {...props, name: `${name}-${i}`}
}
return (
<div>
{Children.map(children, (child,i) => (
<div key={i}>newChild(child)</>
))}
</div>
)
}
This simple example updates (or adds) a name prop to every child component. While you can loop over the children prop directly like this:
{children.map((child,i) => (
<div key={i}>newChild(child)</>
))}
As soon as you pass a single element in, it's treated as an object, and doesn't work with the vanilla js .map() function. Which I thought was the entire reason for Children.map() to exist! Clearly I could use Children.toArray(), but I'd still be using the legacy API.
So, what's the alternative here? The examples they post all conveniently leave out any attempts of actually iterating over the children prop. Are we supposed to stop iterating over it now? Just pass it in and return it? I understand the pitfalls they discuss (eg. the grandchildren aren't rendered), but sometimes there's valid reasons for needing to access each child node. My form-with-lots-of-components example is just one.
I could probably cobble something together that checks if a variable is an object or array, but holy shit, that feels like reinventing a wheel that I'm already using.
Using any should generally be avoided as you're losing the type info.
Instead use React.ReactElement<P> in the function parameter type:
React.Children.map(this.props.children, (child: React.ReactElement<ChildPropTypes>) => child.props.bar)
However, in Typescript, the type of x is React.ReactElement so I have no access to props. Is there some kind of casting that I need to do?
Yes. Also prefer the term assertion. A quick one:
React.Children.map(this.props.children, x => (x as any).props.foo)