8

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

2 Answers 2

14

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!

Sign up to request clarification or add additional context in comments.

Comments

0

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} />;

5 Comments

This isn't a render prop, that's just a react child.
@LeonardoPetrucci How is it a react child? I'm passing it in as a prop, not as a child. For the example, I chose to render it inside of a div, but there could be other props, including children, and this render prop could still be passed in. I'm a little surprised at the downvotes since this is a pretty elegant approach.
Yeah you're passing children as a prop, that's not a render prop.
@lpetrucci I don't get it, what's a render prop then? AFAIC when you don't use the "children" prop (which is the one for the semantic notation within parent component's tag), you are using a render prop then. Why is this just a react child if the prop's name is "detailComponent"?
@Umagon old docs about render props legacy.reactjs.org/docs/render-props.html

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.