I am converting from PropTypes to typescript in a large react native project. The code has several components that use child classes for name scoping and organization. A simplified example would be:
const styles = StyleSheet.create({
listItem: ...,
leftItem: ...,
contentItem: ...,
rightItem: ...,
})
const ListItemLeft = ({ children }) => <View style={styles.leftItem}>{children}</View>;
const ListItemContent = ({ children }) => <View style={styles.contentItem}>{children}</View>;
const ListItemRight = ({ children }) => <View style={styles.rightItem}>{children}</View>;
class ListItem extends Component {
render() {
let left = null, right = null, content = null;
const otherChildren = [];
React.Children.forEach(children, child => {
if (child) {
if (child.type.name === Left.name) {
left = child;
} else if (child.type.name === Right.name) {
right = child;
} else if (child.type.name === Content.name) {
content = child;
} else {
otherChildren.push(child);
}
}
});
return (
<div style={styles.listItem}>
{left}
{content}
{right}
{otherChildren}
</div>
);
}
}
ListItem.Left = ListItemLeft;
ListItem.Content = ListItemContent;
ListItem.Right = ListItemRight;
export default ListItem;
Using it from a page then looks something like
<ListItem>
<ListItem.Left>Left content</ListItem.Left>
<ListItem.Content>Main content</ListItem.Cotent>
<ListItem.Right>Right content</ListItem.Right>
<ListItem>
As I am converting this to typescript, I can’t seem to figure out how to test the children to see what type of item they are and assign the variables accordingly.
let left : ListItemLeft | null = null,
right : ListItemRight | null = null,
content : ListItemContent | null = null;
React.Children.forEach<ReactNode>(children, child => {
/* how to test child type and assing to left, right, content or other? /*
});
2
Answers
Thanks Dan, your response helped me to see the forest despite the trees. For anyone else looking for an example, this is what my updated code looks like. As Dan mentioned, React adds the needed type information (the type property) to components, and using the React.isValidElement function makes typescript happy when checking it.
If your existing JS code was working then it would probably continue to do so after migration TypeScript. However TypeScript is likely reporting errors due to some unsafe operations on the child object, since its type is the rather complex
string | number | boolean | React.ReactElement<any, string | React.JSXElementConstructor<any>> | React.ReactFragment | React.ReactPortal | null | undefined
.Changing your if statements to e.g.:
if ((typeof child === 'object') && ('type' in child) && (child.type === ListItemLeft)) {
should be enough to keep TypeScript happy, while also making the component safer if it has children other than components (such as strings).
Note that in general, unlike in most other typed languages, TypeScript objects can’t be queried for their type at runtime since TypeScript is a structurally typed language. This approach only works due to React providing the
type
field.This answer to a similar problem might also be useful.