TrieuNomad
TrieuNomad

Reputation: 1830

Typescript React: How to override default html input element attribute prop types

I'm trying to create a custom "Input" component and extend the default html input attributes (props).

However, I am also trying to override the default 'size' attribute using Typescript's Omit within my own interface definitions, but can't seem to get it to work.

(I've also tried Typescript's Pick/Exclude and a custom Override type, but get same error...)

Similar threads I've already tried:

import React from 'react';

type A = React.HTMLAttributes<HTMLInputElement>;

interface InputProps extends Omit<A, 'size'> {
  size?: 'sm' | 'md' | 'lg';
}

const Input = (props: InputProps) => {
  return <input {...props} />;
};

TS Error

(property) JSX.IntrinsicElements.input: React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>
Type '{ size?: "sm" | "md" | "lg"; defaultChecked?: boolean; defaultValue?: string | number | readonly string[]; suppressContentEditableWarning?: boolean; ... 250 more ...; onTransitionEndCapture?: (event: TransitionEvent<...>) => void; }' is not assignable to type 'DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>'.
  Type '{ size?: "sm" | "md" | "lg"; defaultChecked?: boolean; defaultValue?: string | number | readonly string[]; suppressContentEditableWarning?: boolean; ... 250 more ...; onTransitionEndCapture?: (event: TransitionEvent<...>) => void; }' is not assignable to type 'InputHTMLAttributes<HTMLInputElement>'.
    Types of property 'size' are incompatible.
      Type 'string' is not assignable to type 'number'.
        Type 'string' is not assignable to type 'number'.ts(2322)

Upvotes: 3

Views: 13545

Answers (3)

ffcabbar
ffcabbar

Reputation: 371

You don't have to replace your interface.

For example

export interface InputProps extends Omit<A,'size'> {
 size?: InputSize;
 /* other properties */
}

type A = React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>,HTMLInputElement>;
type InputSize = 'xs' | 'sm' | 'md' | 'lg';

const Input = (props: InputProps) => {
 const { style, className, size = 'md', ...rest } = props;
 /* you don't have to pass a default value to size */

 return <input {...rest} />;
};

Upvotes: 1

Linda Paiste
Linda Paiste

Reputation: 42228

The design flaw here isn’t the props that you’re accepting into your custom Input component. It is that you are are then passing those same props down to the HTML input component, even though you already know that your size property differs from the expected size property. That is what is reflected in the error that you’ve pasted here.

You do need to fix your type definition as explained by @silent.

But after doing that, you need to do something inside your Input component to map your custom size strings into numbers before you can pass them down to input.

Edit:

Based on your comment you have said that these are CSS class names. So they need to be passed down to the input component in the className property, not the size property.

// sets default size to 'md' - you can change or remove this
const Input = ({size = 'md', className, ...props}: InputProps) => {
    // combine with existing class names
    const newClass = className + ' ' + size; // could trim redundant spaces but don't need to

    return (
        <input 
            {...props} 
            className={newClass}
        />;
};

You don't need to pass anything down to size. In fact this component cannot ever set anything to the size property on the input because we took size out when we used the spread operator ({size = 'md', className, ...props}). ...props is a "rest" parameter meaning that it has all the properties not otherwise extracted, so it will not include the size or className from the InputProps.

Upvotes: 2

SILENT
SILENT

Reputation: 4268

Replace interface InputProps.... with

type InputProps = {
 size?: 'sm' | 'md' | 'lg';
} & Omit<A, 'size'>;

Based on OP's comment,

const Input = ({ size, ...props }: InputProps) => {
  // if(size) do something...
  return <input {...props} />;
};

Upvotes: 7

Related Questions