jonhobbs
jonhobbs

Reputation: 27952

How to use Render Props in functional component using typescript

I'm trying to convert my React app (create-react-app) to use Typescript and I've hit a hurdle with a component that uses render props.

Here's a simplified version which still has the error...

import React from 'react'

type Props = {
    fullWidth?: boolean,
    renderRightSection?: React.ReactNode
}

const Component = React.forwardRef<HTMLInputElement, Props>((props, ref) => {

    let {fullWidth, renderRightSection, ...inputProps} = props

    return (

        <InputWrapper fullWidth={props.fullWidth}>

            <input {...inputProps} ref={ref} />

            {renderRightSection && renderRightSection()}

        </InputWrapper>

    )

})

The error is with the renderRightSection() bit and looks like this:

let renderRightSection: string | number | true | {} | React.ReactElement<any, string | ((props: any) => React.ReactElement<any, string | ... | (new (props: any) => React.Component<any, any, any>)> | null) | (new (props: any) => React.Component<...>)> | React.ReactNodeArray | React.ReactPortal
This expression is not callable.
  No constituent of type 'string | number | true | {} | ReactElement<any, string | ((props: any) => ReactElement<any, string | ... | (new (props: any) => Component<any, any, any>)> | null) | (new (props: any) => Component<...>)> | ReactNodeArray | ReactPortal' is callable.ts(2349)

Any help would really be appreciated.

Edit:

To show how I am using the component in a form:

<Textbox 
    name="username" 
    type="text" 
    fullWidth={true} 
    placeholder="Choose a Username" 
    renderRightSection={() => (
        <TextboxBadge>
            {usernameAvailable && <span>Available</span>}
            {!usernameAvailable && <span>Taken</span>}
        </TextboxBadge>
    )}
/>

Edit:

And here's what it's used for, rendering a block of JSX on the right hand side of the textbox.

Screenshot

Upvotes: 8

Views: 19306

Answers (2)

DustinA
DustinA

Reputation: 517

I used the accepted answer, and it worked, but I didn't like it. I was able to get this working by typing the component being passed in as ReactNode and then calling it like this:

interface MyComponentProps {
    detailComponent: ReactNode;
}

const MyComponent = ({ detailComponent }: MyComponentProps) => {
    return <div>{detailComponent}</div>;
};

export default MyComponent;

This is what it looks like when called from the parent:

const myComponent = <OfficeDetail />;
    
return <MyComponent detailComponent={myComponent} />;

Upvotes: 0

jonhobbs
jonhobbs

Reputation: 27952

I solved the problem. It seems that when creating the type for the component the render prop needs to be a function which returns JSX. I had no idea that you could do this:

type Props = {
    fullWidth?: boolean,
    className?: string,
    renderRightSection?: () => JSX.Element
}

But it worked!

Upvotes: 14

Related Questions