KingKerosin
KingKerosin

Reputation: 3841

Render different react-components depending on property-value

I'm trying to build an input-component in ReactJs using typescript. An input can have different types, e.g. text, date, select or textarea.

Depending on the given type the rendered control can be different. If set to text, <input type="text" /> should be returned. On the other hand, if the type is select, <select></select> should be returned.

These are my props:

export interface IInputProps {
    type?: "text" | "checkbox" | "date" | "select"| "textarea";
}

In my render-function I now try to figure out, what should be returned:

render(): JSX.Element {
    const { type } = this.props;

    let Element: JSX.Element = null;

    switch (type) {
        case "text":
            Element = <input type={type} />;
            break;
        case "select":
            const { children } = this.props.selectProps;
            Element = <select>{children}</select>;
            break;
        default:
            throw new Error(`The type ${type} is currently not implemented. If needed, do so.`);
    }

    const classes = classNames("form-control");

    // return the markup of the final element
    return (
        <React.Fragment>
            { /* below the error is thrown */ }
            <Element ref={/* some internal handling here */} className={classes} /> 
        </React.Fragment>
    );
}

However, this code throws

Error TS2604 (TS) JSX element type 'Element' does not have any construct or call signatures.

Note that the example is a simplified version of the problem. There will be more type-specific properties, e.g. attributes rows and cols for textarea.

What would be the correct way to achieve this? What type must Element be? Also note, that I have to set attributes, which may not be available, e.g. type is not available on <select>.

Upvotes: 1

Views: 2070

Answers (1)

azium
azium

Reputation: 20614

You're doubling up on React.createElement calls. Remember that:

let element = <div />

turns into this:

let element = React.createElement('div')

so your code:

Element = <input type={type} />;

and:

<Element ref={/* some internal handling here */} className={classes} /> 

is like doing:

let Element = React.createElement(React.createElement('input'))

which doesn't make sense.

Solution:

1) evaluate your already created element:

return (
    <React.Fragment>
        {Element} 
    </React.Fragment>
);

This means any props you want to add, like ref, you'll need to add when creating the element:

Element = <input type={type} ref={...} />;

This may seem arduous, luckily you can just make a component on the fly:

// notice the function, not directly calling createElement
Element = props => <input type={type} {...props} />;

Then this code will work:

return (
    <React.Fragment>
        <Element ref={/* some internal handling here */} className={classes} /> 
    </React.Fragment>
);

update: I almost never use ref so I forgot about the stateless warning. quick fix:

// notice the component, not directly calling createElement
Element = class extends React.Component {
  render() {
    return <input type={type} {...this.props} />;
  }
}

Upvotes: 1

Related Questions