How to define typescript for React.Children.map
Asked Answered
I

1

22

I have a function that looks at the provided children and if a particular element type is found, it adds some properties to it automatically.

The function is called like this:

render () {

    const { children, name, className } = this.props;

    return (
        <div className={className}>
            {this.enrichRadioElements(children, name)}
        </div>
    )
}

and it is implemented like this:

enrichRadioElements = (children: Array<any>, name: string) => (
    React.Children.map(children, child => {
        if (!React.isValidElement(child)) {
            return child;
        }

        //@ts-ignore
        if (child.props.children) {
            child = React.cloneElement(child, {
                //@ts-ignore
                children: this.enrichRadioElements(child.props.children, name)
            });
        }

        if (child.type === Radio) {
            return React.cloneElement(child, { 
                onChange: this.handleFieldChange,
                selectedValue: this.state.selectedValue,
                name: name
            })
        }
        else {
            return child;
        }
    })
)

The two //@ts-ignore comments are what I'm trying to get rid of by writing code that will satisfy typescript. If I remove the first one, the error message I see is this:

Property 'children' does not exist on type '{}'.(ts-2339)

How can I properly modify my code so I can remove the //@ts-ignore comments? I did go to the definition of child.props and I found this:

interface ReactElement<P = any, T extends string | JSXElementConstructor<any> = string | JSXElementConstructor<any>> {
    type: T;
    props: P;
    key: Key | null;
}

which looks to have a 'props' of type any (if I'm reading it correctly), but typescript doesn't recognize the children property.

Ilarrold answered 1/4, 2019 at 22:0 Comment(0)
D
30

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
    }
  })
De answered 3/4, 2019 at 2:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.