I have created my custom Autocomplete (Autosuggestions) component. Everything works fine when I pass a hardcoded array of string to autocomplete component, but when I try to pass data from API as a prop, nothing is showing for the first time I search. Results are showing each time exactly after the first time

I have tried different options but seems like when a user is searching for the first time data is not there and autocomplete is rendered with an empty array. I have tested same API endpoint and it's returning data as it should every time you search.

Home component which holds Autocomplete

        const filteredUsers = this.props.searchUsers.map((item) => item.firstName).filter((item) => item !== null);

        const autocomplete = (
            <AutoComplete
                items={filteredUsers}
                placeholder="Search..."
                label="Search"
                onTextChanged={this.searchUsers}
                fieldName="Search"
                formName="autocomplete"
            />
        );

AutoComplete component which filters inserted data and shows a list of suggestions, the problem is maybe inside of onTextChange:

export class AutoComplete extends Component {
    constructor(props) {
        super(props);
        this.state = {
            suggestions: [],
            text: '',
        };
    }

    // Matching and filtering suggestions fetched from the backend and text that user has entered
    onTextChanged = (e) => {
        const value = e.target.value;
        let suggestions = [];
        if (value.length > 0) {
            this.props.onTextChanged(value);
            const regex = new RegExp(`^${value}`, 'i');
            suggestions = this.props.items.sort().filter((v) => regex.test(v));
        }
        this.setState({ suggestions, text: value });
    };

    // Update state each time user press suggestion
    suggestionSelected = (value) => {
        this.setState(() => ({
            text: value,
            suggestions: []
        }));
    };

    // User pressed the enter key
    onPressEnter = (e) => {
        if (e.keyCode === 13) {
            this.props.onPressEnter(this.state.text);
        }
    };

    render() {
        const { text } = this.state;
        return (
            <div style={styles.autocompleteContainerStyles}>
                <Field
                    label={this.props.placeholder}
                    onKeyDown={this.onPressEnter}
                    onFocus={this.props.onFocus}
                    name={this.props.fieldName}
                    formValue={text}
                    onChange={this.onTextChanged}
                    component={RenderAutocompleteField}
                    type="text"
                />
                <Suggestions
                    suggestions={this.state.suggestions}
                    suggestionSelected={this.suggestionSelected}
                    theme="default"
                />
            </div>
        );
    }
}

const styles = {
    autocompleteContainerStyles: {
        position: 'relative',
        display: 'inline',
        width: '100%'
    }
};

AutoComplete.propTypes = {
    items:  PropTypes.array.isRequired,
    placeholder: PropTypes.string.isRequired,
    onTextChanged: PropTypes.func.isRequired,
    fieldName: PropTypes.string.isRequired,
    label: PropTypes.string.isRequired,
    onPressEnter: PropTypes.func.isRequired,
    onFocus: PropTypes.func
};

export default reduxForm({
    form: 'Autocomplete'
})(AutoComplete);

Expected results: Every time user use textinput to search, he should get results of suggestions Actual results: First-time user use textinput to search, he doesn't get data. Only after first-time data is there

1 Answers

1
smashed-potatoes On Best Solutions

It works when it is hardcoded but not when using your API because your filtering happens in onTextChanged. When it is hardcoded your AutoComplete has a value to work with the first time onTextChanged (this.props.items.sort().filter(...) is called but with the API your items prop will be empty until you API returns - after this function is done.

In order to handle results from your API you will need do the filtering when the props change. The react docs actually cover a very similar case here (see the second example as the first is showing how using getDerivedStateFromProps is unnecessarily complicated), the important part being they use a PureComponent to avoid unnecessary re-renders and then do the filtering in the render, e.g. in your case:

render() {
  // Derive your filtered suggestions from your props in render - this way when your API updates your items prop, it will re-render with the new data
  const { text } = this.state;
  const regex = new RegExp(`^${text}`, 'i');
  suggestions = this.props.items.sort().filter((v) => regex.test(v));

  ...
  <Suggestions
    suggestions={suggestions}
    ...
  />
  ...
}