input 자동 완성 구현하기

sham·2024년 10월 28일
0

SkyScope 개발일지

목록 보기
8/12

다음 토이프로젝트의 개발 기록이다.

Kakao 지도 API, react-kakao-maps-sdk, Kakao Local API, 공공 data 포털을 활용했다.

개요

입력을 할 때마다 연관 검색어가 자동으로 뜨게끔 하는 기능은 어렵지 않게 찾아볼 수 있는 기능이다. 카카오 Local API를 활용한 자동 완성을 구현해보자.

코드

useAutoSearch - 커스텀 훅

  • isAutoSearch - 자동완성 창 렌더링을 결정하는 boolean
  • searchWord - 현재 input에 있는 문자열, 입력할 때마다 실시간으로 연관 검색어를 생성한다.
  • searchAutoList - 현재 searchWord로 검색했을 때 나오는 연관 검색어들의 배열. 카카오 API의 검색을 사용한다. (keywordSearch)
  • focusIndex - searchAutoList를 조작할 때 사용될 index
  • handleChangeInput - input의 onChange 핸들링 이벤트.
    • 입력 할 떄마다 searchWord를 업데이트한다.
    • 카카오 sdk 모듈인 ps의 검색 메서드를 호출하고 현재 현재 input의 문자열을 검색한다.
    • 문자열이 비어있거나 검색결과가 없으면 isAutoSearch를 false로 업데이트한다.
  • handleChangeFocus - input의 onKeyPress 핸들링 이벤트.
    • 화살표 위, 아래를 누를 때마다 focusIndex를 업데이트한다. searchAutoList에 곧바로 접근하기 위해 searchAutoList의 길이에 나머지 연산을 한 값을 인덱스로 맞춰준다.
    • enter 입력 시 새로고침 되는 것을 막기 위해 preventDefault를 설정하고, isAutoSearch가 true일 때 searchAutoList의 인덱스에 해당하는 문자열을 searchWord로 업데이트한다.
import { set } from 'date-fns';
import { useState, useCallback } from 'react';

const useAutoSearch = () => {
  const [isAutoSearch, setIsAutoSearch] = useState<boolean>(false);
  const [searchWord, setSearchWord] = useState<string>('');

  const [searchAutoList, setSearchAutoList] = useState<string[]>([]);
  const [focusIndex, setFocusIndex] = useState(-1);

  const handleChangeInput = (e: React.ChangeEvent<HTMLInputElement>) => {
    e.preventDefault();

    const value = e.target.value;
    setSearchWord(value);

    if (value.length) {
      const ps = new kakao.maps.services.Places();
      ps.keywordSearch(value, (data, status, pagination) => {
        if (status === kakao.maps.services.Status.OK) {
          const list = data.map((item: any) => item.place_name).slice(0, 5);
          setSearchAutoList(list);
          setIsAutoSearch(true);
          setFocusIndex(-1);
        } else {
          setIsAutoSearch(false);
        }
      });
    } else setIsAutoSearch(false);
  };

  const handleChangeFocus = (e: React.KeyboardEvent<HTMLInputElement>) => {
    if (e.key === 'ArrowDown') {
      const index = (focusIndex + 1) % searchAutoList.length;
      setFocusIndex(index);
    } else if (e.key === 'ArrowUp') {
      const index = (focusIndex - 1 + searchAutoList.length) % searchAutoList.length;
      setFocusIndex(index);
    } else if (e.key === 'Enter') {
      e.preventDefault();
      if (!isAutoSearch) return;
      setSearchWord(searchAutoList[focusIndex]);
      setIsAutoSearch(false);
      setSearchAutoList([]);
      setFocusIndex(-1);
    }
  };

  const onClickAutoGroup = (place: string) => {
    setSearchWord(place);
    setIsAutoSearch(false);
    setSearchAutoList([]);
  };

  const onClickSearchButton = () => {
    setSearchWord('');
    setIsAutoSearch(false);
  };

  return {
    isAutoSearch,
    searchWord,
    searchAutoList,
    focusIndex,
    handleChangeInput,
    handleChangeFocus,
    onClickSearchButton,
    onClickAutoGroup,
  };
};

export default useAutoSearch;

MapPage - 구현 부

import { useState, useRef, useCallback } from 'react';
import { Map, MapMarker } from 'react-kakao-maps-sdk';

import { LoadingState } from '@src/Component';
import { useKakaoLoader, useMapMarker, useAutoSearch } from '@src/Hook';

import { Form, Button, ListGroup } from 'react-bootstrap';
import styled from 'styled-components';

const MapPage = () => {

  const {
    isAutoSearch,
    searchWord,
    searchAutoList,
    focusIndex,
    handleChangeInput,
    handleChangeFocus,
    onClickSearchButton,
    onClickAutoGroup,
  } = useAutoSearch();

  const mapRef = useRef<kakao.maps.Map>(null);

  const [curPage, setCurPage] = useState<number>(1);
  const [maxPage, setMaxPage] = useState<number>(1);

  const insertAddress = () => {
    setCurPage(1);
    searchPlaces(searchWord, 1, setMaxPage);
    onClickSearchButton();
  };

  return (
    <MapContainer>
 
      <FormContainer>
        <Form>
          <Form.Control
            size='lg'
            type='text'
            placeholder='날씨를 알고 싶은 장소는?'
            value={searchWord}
            onChange={handleChangeInput}
            onKeyDown={handleChangeFocus}
          />
        </Form>
        <Button onClick={insertAddress}>확인</Button>
      </FormContainer>

      {isAutoSearch && (
        <ListGroupContainer>
          <ListGroup>
            {searchAutoList.map((item: string, index: number) => (
              <ListGroup.Item
                className={`${focusIndex === index && 'focus'}`}
                key={'searchAutoList' + index}
                onClick={() => onClickAutoGroup(item)}
              >
                {item}
              </ListGroup.Item>
            ))}
          </ListGroup>
        </ListGroupContainer>
      )}
 
    </MapContainer>
  );
};

export default MapPage;
profile
씨앗 개발자

0개의 댓글

관련 채용 정보