React.Children.map()
is missing a good example, I don't understand how to type it:
/*
<CheckboxGroup ...>
<Checkbox ... />
<Checkbox ... />
<Checkbox ... />
</CheckboxGroup>
*/
// Child
class Checkbox extends React.Component<CheckboxProps, void> {
// ...
}
// Parent
class CheckboxGroup extends React.Component<CheckboxGroupProps, void> {
// ...
render() {
const checkboxes = React.Children.map(this.props.children, checkbox =>
React.cloneElement(checkbox, {
name: this.props.name,
checked: this.props.checkedValues.includes(checkbox.props.value),
onChange: this.handleCheckboxChange.bind(this)
})
);
return (
<div>
{checkboxes}
</div>
);
}
}
(very common example, see http://stackoverflow.com/a/32371612/990356)
this.props.children
?React.cloneElement()
?React.Children.map()
?Most of the time I end up with the following error: Type 'string' is not assignable to type 'ReactElement<any>'
, see #8131
please send a pull request. I'll review it.
@vvakame I can't, I don't understand how it is supposed to work
me too. please make conversations with definition authors.
Ping @vsiao @thasner @basarat @pspeter3 @beckend
Ping/authors list based on:
you'll need to type-annotate or type-assert checkbox
explicitly because there's no guarantee that this.props.children
does not contain non-ReactElement values. for example:
React.Children.map(this.props.children, (checkbox: React.ReactElement<CheckboxProps>) =>
React.cloneElement(checkbox) // should be ok
);
@vsiao thx a lot, it works!
Question:
const checkboxes = React.Children.map(this.props.children, checkbox =>
React.cloneElement(checkbox, {
name: this.props.name,
checked: this.props.checkedValues.includes(checkbox.props.value),
onChange: this.handleCheckboxChange.bind(this)
})
);
Solution:
const checkboxes = React.Children.map(props.children, (checkbox: React.ReactElement<CheckboxPropsInternal>) =>
React.cloneElement(checkbox, {
checked: props.checkedValues.includes(checkbox.props.value),
onChange: handleCheckboxChange
})
);
@tkrotoff you could also do this:
React.Children.map(this.props.children, (child: number) => {
return child + 1
})
This and the suggested solution are just taking advantage of the fact that React.Children.map
is not strongly typed. You might as well have used any
instead of the Checkbox
type since there is no guarantee that that will be the type of the children. This means that you are likely to have runtime errors if anyone miss-uses the component by passing in a string.
How about this:
class Slot extends React.PureComponent {
render () {
return this.props.children
}
}
const isReactElement = (obj: {}): obj is React.ReactElement<{}> => {
return obj.hasOwnProperty('type')
}
const isSlot = (obj: {}): obj is Slot => {
return isReactElement(obj) && obj.type === Slot
}
const getSlots = (children: React.ReactNode) => React.Children.map(children, (child: React.ReactChild): JSX.Element | null => {
if (isReactElement(child) && isSlot(child)) {
return child
}
return null
})
class ComponentWithSlots extends React.PureComponent {
render () {
const [header, footer] = getSlots(this.props.children)
return (
<div>
{header}
<div>
<h1>Welcome</h1>
<p>This is my lovely component with slots</p>
</div>
{footer}
</div>
)
}
}
class MyComponent extends React.PureComponent {
render () {
return (
<ComponentWithSlots>
<Slot>My Header!</Slot>
<Slot>
<div>github: @variousauthors</div>
</Slot>
</ComponentWithSlots>
)
}
}
Which will render:
My Header!
Welcome
This is my lovely component with slots
github: @variousauthors
This approach takes advantage of the type information we _do_ have, which is that a ReactChild
might be a ReactElement
(I've used type
to detect this, but you could be more careful if you wanted). We can also be more descriptive and elaborate that Slot
. I saw a UI library implement a pattern where classes had their own slots attached. Something like:
<Dropdown>
<Dropdown.Header>
<FancyIcon>My Header!</FancyIcon>
</Dropdown.Header>
{dropdownItems}
</Dropdown>
This way the user is clear on the meaning of each slot. The rest of the children (those not parsed out as slots) are left in a collection called children
and rendered a la cart, allowing the user to still use children
normally while reserving special behaviour for the custom slots.
EDIT: React apparently has a method React.isValidElement
which you can use in place of my custom isReactElement
.
EDIT: Something else you can do is type the children
property on your component, so that a user is only allowed to pass in Checkboxes as children. This will help avoid those runtime errors.
Most helpful comment
Question:
Solution: