Create Typescript interface with all properties for concrete JSX component

Viewed 400

I am making a ControlledInputComponent which has a label and an input inside a div. I wanted to make it as customizeable as possible because I will be using in diverse ways. I have currently included everything I think is needed but it would be nice to be able to accept all other kinds of properties to add.

//These are the classes

const ControlledInputComponentByLabelAndInput = function (props: IInputByComponentAndLabelProps): JSX.Element {
    const {label, input, className} = props;

    return (
        <div className={turnToString(className)}>
            {label}
            {input}
        </div>
    );
}

export const ControlledInputComponent = function (props: IInputProps): JSX.Element {
    const {name, className, onChange, inputOptions = {}, labelOptions = {}} = props;
    const {value = "", type = "text", id: inputId = name, placeholder = name} = inputOptions;
    const {content: labelContent = name} = labelOptions;

    const label = (<label htmlFor={name}>{labelContent}</label>);
    const input = (<input type={type} name={name} value={value} placeholder={placeholder} id={inputId} onChange={onChange}/>);

    return (
        <ControlledInputComponentByLabelAndInput className={className} label={label} input={input}></ControlledInputComponentByLabelAndInput>
    );
}

//These are the current interfaces

interface IInputByComponentAndLabelProps {
    className?: ClassName;
    label: JSX.Element;
    input: JSX.Element;
}

export interface IInputProps {
    name: string;
    className?: ClassName;
    onChange: IEventHandler;
    labelOptions?: {content?: string};
    inputOptions?: {type?: string, value?: string, id?: string, placeholder?: string};
}

I want to include all kinds of other options in labelOptions which are applicable to the label component (which are the ones applicable to the label HTML element). I want to do the same for the input. Is that somehow possible with TypeScript? I could do it with indexers but that would not tell me that I need label options. Is there an option to do what I've done below but with additionalLabelOptions being of interface ReactLabelComponent (or whatever it would be called).

const additionalLabelOptions = Object.entries(labelOptions).filter(([key])=>key!=="content");

    const label = (<label htmlFor={name} {...additionalLabelOptions}>{labelContent}</label>);
1 Answers

You can make your interfaces with this logic:

type LabelProps = React.HTMLProps<HTMLLabelElement>;

type InputProps = React.HTMLProps<HTMLInputElement>;

interface IInputByComponentAndLabelProps {
    className?: ClassName;
    label: JSX.Element;
    input: JSX.Element;
}

export interface IInputProps {
    name: string;
    className?: ClassName;
    onChange: IEventHandler;
    labelOptions?: LabelProps;
    inputOptions?: InputProps;
}

Then you can pass all of props using rest like this:

export const ControlledInputComponentByLabelAndInput: React.FC<IInputByComponentAndLabelProps> = ({
    label,
    input,
    className
}) => (
    <div className={className}>
        {label}
        {input}
    </div>
);

export const ControlledInputComponent: React.FC<IInputProps> = (props) => {
    const {
        name,
        className,
        onChange,
        inputOptions = {},
        labelOptions = {}
    } = props;
    const {
        value = "",
        type = "text",
        id: inputId = name,
        placeholder = name,
        ...inputRest
    } = inputOptions;
    const { content: labelContent = name, ...labelRest } = labelOptions;

    const labelElement = (
        <label htmlFor={name} {...labelRest}>
            {labelContent}
        </label>
    );
    const inputElement = (
        <input
            type={type}
            name={name}
            placeholder={placeholder}
            id={inputId}
            onChange={onChange}
            {...inputRest}
        />
    );

Finally you can use your input like this:

export const App = () => (
    <ControlledInputComponent
        onChange={(e) => console.log(e.target.value)}
        inputOptions={{
            type: "number",
            id: "newInput",
            defaultValue: "12",
            style: { color: "red" }
        }}
        labelOptions={{ htmlFor: "newInput", style: { color: "green" } }}
        name="test"
    />
);

CodesandBox

Related