Autosuggest renderInputComponent - inputProps types of property 'onChange' are incompatible

Viewed 1158

I'm having this Typescript error when using react-autosuggest with a custom input styled-component.

Types of property 'onChange' are incompatible. Type '(event: FormEvent, params: ChangeEvent) => void' is not assignable to type '(event: ChangeEvent) => void'.

Code: (note that it's not complete, just relevant portions)

// styled.ts
export const Input = styled.input`
  // styles
`;

// index.tsx
function renderInput(
  inputProps: Autosuggest.InputProps<SuggestionSearch>,
  placeholder: string
) {
  // --> error here
  return <Input {...inputProps} placeholder={placeholder} />;
}

const MyComponent = () => {
  const autosuggestRef = React.useRef<
    Autosuggest<SuggestionSearch, SuggestionSearch>
  >(null);

  const onChange = (event: React.FormEvent, { newValue }: ChangeEvent) =>
    setValue(newValue);

  const inputProps = {
    value,
    onChange,
    onKeyPress: onEnterPress,
  };

  return (
    <Autosuggest
      ref={autosuggestRef}
      renderInputComponent={props => renderInput(props, placeholder)}
      inputProps={inputProps}
      // other props
    />
  )
}

Not sure how to fix this, as Autosuggest onChange function overrides the base input onChange prop.

3 Answers

I spent about all night on this problem myself. It seems that this is a bug. Here is the signature of the InputProps:

    interface InputProps<TSuggestion>
        extends Omit<React.InputHTMLAttributes<any>, 'onChange' | 'onBlur'> {
        onChange(event: React.FormEvent<any>, params: ChangeEvent): void;
        onBlur?(event: React.FocusEvent<any>, params?: BlurEvent<TSuggestion>): void;
        value: string;
    }

It seems we'll need to create our own Input component that take in this type of onChange signature or wrap this onChange in the standard onChange.

So, this is my approach:

const renderInputComponent = (inputProps: InputProps<TSuggestion>): React.ReactNode => {
    const onChangeHandler = (event: React.ChangeEvent<HTMLInputElement>): void => {
      inputProps.onChange(event, { newValue: event.target.value, method: 'type' });
    };
   return <Input {...inputProps} onChange={onChangeHandler} />;
}

Two potential bugs:

  • Notice that the event returned is React.ChangeEvent and not the React.FormEvent. Minor difference but it can be a problem in runtime if you actually use it and particular enough about the event.
  • The returned params: ChangeEvent only account for type event method. If you want others, like click, esc, etc..., you must supplement your own (via onKeyUp for example, and then call `onChange(...{method: 'esc'})

According to doc:

Note: When using renderInputComponent, you still need to specify the usual inputProps. Autosuggest will merge the inputProps that you provide with other props that are needed for accessibility (e.g. 'aria-activedescendant'), and will pass the merged inputProps to renderInputComponent.

you don't have to do this:

renderInputComponent={props => renderInput(props, placeholder)}

Simply pass the placeholder directly into your inputProps and that would be given right back to you in the renderInputComponent.

  const inputProps = {
    value,
    onChange,
    onKeyPress: onEnterPress,
    placeholder: 'something!`
  };

My problem is the onChange() on the renderInputComponent does not trigger the onSuggestionsFetchRequested.

If I do not use custom input (via renderInputComponent), then everything works fine - onSuggestionsFetchRequested got triggered and suggestion list showing.

Main component:

    const MySearchBox: FC<MySearchBoxProps> = (props) => {
    
    // Autosuggest will pass through all these props to the input.
    const inputProps = {
        placeholder: props.placeholder,
        value: props.value,
        onChange: props.onChange
    };

    return (
        <Autosuggest
            suggestions={props.suggestions}
            onSuggestionsFetchRequested={props.onSuggestionsFetechRequested}
            onSuggestionsClearRequested={props.onSuggestionsClearRequested}
            getSuggestionValue={props.getSuggestionValue}
            shouldRenderSuggestions={(value) => value.trim().length > 2}
            renderSuggestion={(item: ISuggestion) => {
                <div className={classes.suggestionsList}>{`${item.text} (${item.id})`}</div>;
            }}
            renderInputComponent={(ip: InputProps<ISuggestion>) => {
                const params: InputProps<ISuggestion> = {
                    ...ip,
                    onChange: props.onChange
                };
                return renderInput(params);
            }}
            inputProps={inputProps}
            
        />
    );
};

export { MySearchBox };

Custom Input Component:

import React, { FC, ChangeEvent } from 'react';
import SearchIcon from '@material-ui/icons/Search';
import useStyles from './searchInput.styles';
import { Input } from '@material-ui/core';

type SearchInputBoxProps = {
    loading?: boolean;
    searchIconName: string;
    minWidth?: number;
    placeHolder?: string;
    onChange?: (e: ChangeEvent, params: { newValue: string; method: string }) => void;
};

const SearchInputBox: FC<SearchInputBoxProps> = (props) => {
    const classes = useStyles();

    return (
        <div className={classes.root}>
            <div className={classes.searchIcon}>
                <SearchIcon />
            </div>

            <Input
                placeholder={props.placeHolder}
                classes={{
                    root: classes.inputContainer,
                    input: classes.inputBox
                }}
                inputProps={{ 'aria-label': 'search' }}
                onChange={(e) => {
                    const params: { newValue: string; method: string } = {
                        newValue: e.target.value,
                        method: 'type'
                    };
                    console.log(`e: ${JSON.stringify(params)}`);
                    if (props.onChange) props.onChange(e, params);
                }}
                //onMouseOut={(e) => alert(e.currentTarget.innerText)}
            />
        </div>
    );
};

export { SearchInputBox };

Render Function:

const renderInput = (inputProps: InputProps<ISuggestion>): React.ReactNode => {
    return <SearchInputBox loading={false} onChange={inputProps.onChange} placeHolder={inputProps.placeholder} searchIconName={'search'} {...inputProps.others} />;
};
    

I had same problem.

I just had to cast HTMLElement into HTMLInputElement Like below

const target = e.currentTarget as HTMLInputElement

Then, it works fine when you do

target.value
Related