Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to define typescript for React.Children.map

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.

like image 430
Notre Avatar asked Apr 01 '19 22:04

Notre


People also ask

How do you pass children in React TypeScript?

By invoking them between the opening and closing tags of a JSX element, you can use React children for entering data into a component. The React children prop is an important concept for creating reusable components because it allows components to be constructed together.

How do you define children in React?

children is a special prop, automatically passed to every component, that can be used to render the content included between the opening and closing tags when invoking a component. These kinds of components are identified by the official documentation as “boxes”.

What is ReactNode TypeScript?

The ReactFragment , which is included in the ReactNode type, includes an empty interface. Due to the way that TypeScript handles excess property checks, this means that the ReactNode type will accept any object except an object literal. For almost all intents and purposes, it is functionally equivalent to an any type.

How do you pass props in functional component TypeScript?

To pass a function as props in React TypeScript: Define a type for the function property in the component's interface. Define the function in the parent component. Pass the function as a prop to the child component.


1 Answers

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
    }
  })
like image 146
Donovan Hiland Avatar answered Nov 09 '22 06:11

Donovan Hiland