DEV Community

Ruslan Sydorovych
Ruslan Sydorovych

Posted on

Autosuggest with Rest API issue on React

I use Autosuggest component (https://github.com/moroshko/react-autosuggest) in my React website. So, on the onChange event, I call serviceOsago to get the city/region/country data. Full data are too heavy to load at once, it takes about 10 seconds, so I get it partially by using onChange event. But the problem is when I type a city name for example: "Стрий", it does not add it to the Autosuggest and does not display it.

  const onChange = (event, {newValue, method}) => {
    setValue(newValue);
    serviceOsago.getCityCode(newValue).then((value:any) => {
      setCities(value);
    });
  };
Enter fullscreen mode Exit fullscreen mode

When debugging it in the browser (Network -> Fetch/XHR), I found out it fails to add "Стрий" to Autosuggest due to request delay. For example, when I typed "Стрий", it still loads the "Стри" old call.

Code:

import React, { useState } from 'react';
import Autosuggest from 'react-autosuggest';
import {useDispatch, useSelector} from 'react-redux';
import {
  getErrorCity,
  getLoadingCity,
  getRegisterCity,
  setData,
  setError
} from '../../../redux-data/city/cityReducer';
import {getInstanceError} from '../../../utils/getInstanceError';
import theme from './AutoComplete.module.css';
import SquareLoader from 'react-spinners/SquareLoader';
import {config} from '../../../assets/config';
import serviceOsago from '../../../service/serviceOsago';

export const AutoComplete = (props) => {
  const [touch, setTouch] = useState(false);
  const dispatch = useDispatch();
  const regCity = useSelector(getRegisterCity);
  const loading = useSelector(getLoadingCity);
  const [suggestions, setSuggestions] = useState([] as any[]);
  const [value, setValue] = useState(regCity);
  const [cities, setCities] = useState([]);

  const getSuggestions = (value, cities) => {
    let res = []; 
    const inputValue = value.trim().toLowerCase();
    const inputLength = inputValue.length;

    if (inputValue && inputLength > 0) {
        res = cities.filter(city => {
          return city.name.trim().toLowerCase().startsWith(inputValue);
        });
    }

    return res;
  };

  const renderSuggestion = suggestion => (
    <div>
      {suggestion.nameFull}
    </div>
  );

  const getSuggestionValue = suggestion => suggestion.nameFull;
  const error = useSelector(getErrorCity);
  const errors = {
    regCity: error
  };
  const {
    getClassError,
    getMessageError
  } = getInstanceError(errors);
  const mes = getMessageError('regCity');

  const onSuggestionsFetchRequested = ({ value }) => {
    setSuggestions(getSuggestions(value, cities));
  };

  const onChange = (event, {newValue, method}) => {
    setValue(newValue);
    console.log(newValue);
    serviceOsago.getCityCode(newValue).then((value:any) => {
      setCities(value);
    });
  };

  const onBlur = () => {
    setTouch(true);
    if (value === '') {
        dispatch(setError({
          message: 'Це поле обов\'язкове'
        }));
    }
  };

  const inputProps = {
    placeholder: 'Місто реєстрації автомобіля',
    value,
    onChange,
    onBlur,
    disabled: loading,
    id: props.id
  };

  const renderSuggestionInput = (inputProps) => (
    props.isAutoFocus ? <input {...inputProps} autoFocus /> : <input {...inputProps} />
  );

  const onSuggestionHighlighted = ({ suggestion }) => {
    //console.log(suggestion);
  };

  return (
    <div className={getClassError('regCity', touch)}>
      <Autosuggest 
        suggestions={suggestions}
        onSuggestionsFetchRequested={onSuggestionsFetchRequested}
        getSuggestionValue={getSuggestionValue}
        renderSuggestion={renderSuggestion}
        inputProps={inputProps}
        onSuggestionSelected={(event, data) => {
          dispatch(setData(data.suggestion));
        }}
        theme={theme}
        renderInputComponent={renderSuggestionInput}
        highlightFirstSuggestion={true}
        alwaysRenderSuggestions={true}
        onSuggestionHighlighted={onSuggestionHighlighted}
      />
      {mes && <p>{mes}</p>}
      <SquareLoader loading={loading} size={20} color={config.color} css={config.css}/>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

I think, there are two options how to fix this issue.

  1. Load full data when website is loaded, save somewhere this data (for example: localStorage) and add it to Autosuggest, so it will be available when user is typing. But I think localStorage will fail to save 12 - 15 MB of data. There must be another way to store huge data in React/TypeScript?

  2. Change/improve the onChange event, so it will only trigger API request when a user finished typing and get the full city word instead of calling request on every character. Is it possible to override the onChange event?

What is the proper way to add data from REST API to Autosuggest when a user typing data in React/TypeScript? Thank you.

Top comments (1)

Collapse
 
hunter91151 profile image
Ruslan Sydorovych

Ok. I have fixed this issue by using setTimeout function and useEffect.

  useEffect(() => {
    const delayCityRequest = setTimeout(() => {
      if (value.length > 0) {
          console.log(value);
          serviceOsago.getCityCode(value).then((cities: any) => {
            setCities(cities);
            setSuggestions(getSuggestions(value, cities));
          });
      }
    }, 1000);

    return () => clearTimeout(delayCityRequest);
  }, [value]);
Enter fullscreen mode Exit fullscreen mode

This will allow to intercept the user typing and send request only after 1 second. Now, it works well. This issue is resolved. Thank you.