검색창 자동완성 만들기 (with:react)

19238 단어 React자동완성React

개인프로젝트 검색창을 구현하던 도중 문득 자동완성 기능을 구현하고 싶었다

포털사이트에서 키워드를 검색하면 해당하는 키워드가 포함된 검색어들이 자동완성되는것을 구현해보고자 한다.

해당 파일은 react typescript 버전으로 작성되어있습니다.

검색창 스타일링

css 스타일링은 styled-components를 이용했다.
styled-components의 설명과 사용법은 링크를 참조
링크

Header.tsx

const SearchContainer = styled.div`
  width: 400px;
  height: 45px;
  position: relative;
  border: 0;
  img {
    position: absolute;
    right: 10px;
    top: 10px;
  }
`;

const Search = styled.input`
  border: 0;
  padding-left: 10px;
  background-color: #eaeaea;
  width: 100%;
  height: 100%;
  outline: none;
`;

function Header() {
	<SearchContainer>
      <Search/>
      <img src="assets/imgs/search.svg" alt="searchIcon" />
    </SearchContainer>
}
export default Header;


위 사진처럼 스타일이 적용되었을것이다.

검색창 ts구현

구현 순서
1.우선 검색할 keyword변수를 useState Hooks를 사용하여 만들어준다.
2.검색창에 이벤트 조작을 이용하여 우리가 검색할 keyword변수를 이용해서 검색한다.

const [keyword, setKeyword] = useState<string>("");
const onChangeData = (e:React.FormEvent<HTMLInputElement>) => {
    setKeyword(e.currentTarget.value);
};

자동완성창 스타일링

const AutoSearchContainer = styled.div`
  z-index: 3;
  height: 50vh;
  width: 400px;
  background-color: #fff;
  position: absolute;
  top: 45px;
  border: 2px solid;
  padding: 15px;
`;

const AutoSearchWrap = styled.ul`

`;

const AutoSearchData = styled.li`
  padding: 10px 8px;
  width: 100%;
  font-size: 14px;
  font-weight: bold;
  z-index: 4;
  letter-spacing: 2px;
  &:hover {
    background-color: #edf5f5;
    cursor: pointer;
  }
  position: relative;
  img {
    position: absolute;
    right: 5px;
    width: 18px;
    top: 50%;
    transform: translateY(-50%);
  }
`;
function Header() {
	<SearchContainer>
      <Search value={keyword} onChange={onChangeData}/> //keyword변수와 이벤트조작
      <img src="assets/imgs/search.svg" alt="searchIcon" />
      <AutoSearchContainer>
       <AutoSearchWrap>
        <AutoSearchData>
          <a href="#"></a>
          <img src="assets/imgs/north_west.svg" alt="arrowIcon" />
         </AutoSearchData>
        </AutoSearchWrap>
      </AutoSearchContainer>
    </SearchContainer>
}
export default Header;

자동완성 데이터 fetch

구현 순서
1.api를 통해 받아온 데이터를 keyItems 변수에 담는다.
2.받아온 데이터중에 우리의 키워드가 포함된 데이터만 저장한다.
3.useEffect Hooks를 통해 우리의 api를 호출한다.
오픈 api는 리서치하는 중에
https://gist.githubusercontent.com/Miserlou/c5cd8364bf9b2420bb29/raw/2bf258763cdddd704f8ffd3ea9a3e81d25e2c6f6/cities.json
해당 데이터를 사용했다.

interface autoDatas { //api를 통해 받아온 데이터 interface
  city: string;
  growth_from_2000_to_2013: string;
  latitude:number;
  longitude:number;
  population:string;
  rank:string;
  state:string;
}
function Header() {
	const [keyItems, setKeyItems] = useState<autoDatas[]>([]);
    const fetchData = ()  =>{
    return fetch(
      `https://gist.githubusercontent.com/Miserlou/c5cd8364bf9b2420bb29/raw/2bf258763cdddd704f8ffd3ea9a3e81d25e2c6f6/cities.json`
    )
      .then((res) => res.json())
      .then((data) => data.slice(0,100))
  }

  interface ICity {
    includes(data:string): boolean;
    city?: any;
  }
  const updateData = async() => {
    const res = await fetchData();
    let b = res.filter((list: ICity) => list.city.includes(keyword) === true)
                .slice(0,10);
    // console.log(b);
    setKeyItems(b);
  }
  useEffect(() => {
    updateData();
    },[keyword]) //키워드가 변경되면 api를 호출
    
    ...return tsx파일 생략
}
export default Header;

받아온 자동완성 데이터를 우리의 페이지에 바인딩

function Header() {
...생략
	<SearchContainer>
      <Search value={keyword} onChange={onChangeData}/>
      <img src="assets/imgs/search.svg" alt="searchIcon" />
      {keyItems.length > 0 && keyword && ( //키워드가 존재하고,해당키워드에 맞는 이름이 있을때만 보여주기 
        <AutoSearchContainer>
         <AutoSearchWrap>
           {keyItems.map((search, idx) => (
            <AutoSearchData
              key={search.city}
              onClick={() => {
               setKeyword(search.city);
              }}
            >
            <a href="#">{search.city}</a>
            <img src="assets/imgs/north_west.svg" alt="arrowIcon" />
           </AutoSearchData>
          ))}
          </AutoSearchWrap>
        </AutoSearchContainer>
       )}
    </SearchContainer>
}

결과화면

전체코드

import React from 'react';
import { useEffect } from 'react';
import { useState } from 'react';
import styled from 'styled-components';

const SearchContainer = styled.div`
  width: 400px;
  height: 45px;
  position: relative;
  border: 0;
  img {
    position: absolute;
    right: 10px;
    top: 10px;
  }
`;

const Search = styled.input`
  border: 0;
  padding-left: 10px;
  background-color: #eaeaea;
  width: 100%;
  height: 100%;
  outline: none;
`;


const AutoSearchContainer = styled.div`
  z-index: 3;
  height: 50vh;
  width: 400px;
  background-color: #fff;
  position: absolute;
  top: 45px;
  border: 2px solid;
  padding: 15px;
`;

const AutoSearchWrap = styled.ul`

`;

const AutoSearchData = styled.li`
  padding: 10px 8px;
  width: 100%;
  /* height: 30px; */
  font-size: 14px;
  font-weight: bold;
  z-index: 4;
  letter-spacing: 2px;
  &:hover {
    background-color: #edf5f5;
    cursor: pointer;
  }
  position: relative;
  img {
    position: absolute;
    right: 5px;
    width: 18px;
    top: 50%;
    transform: translateY(-50%);
  }
`;
interface autoDatas {
  city: string;
  growth_from_2000_to_2013: string;
  latitude:number;
  longitude:number;
  population:string;
  rank:string;
  state:string;
}
function Header() {
	const [keyword, setKeyword] = useState<string>("");
    const [keyItems, setKeyItems] = useState<autoDatas[]>([]);
    const onChangeData = (e:React.FormEvent<HTMLInputElement>) => {
    setKeyword(e.currentTarget.value);
  };
  const fetchData = ()  =>{
    return fetch(
      `https://gist.githubusercontent.com/Miserlou/c5cd8364bf9b2420bb29/raw/2bf258763cdddd704f8ffd3ea9a3e81d25e2c6f6/cities.json`
    )
      .then((res) => res.json())
      .then((data) => data.slice(0,100))
  }
  interface ICity {
    includes(data:string): boolean;
    city?: any;
  }
  const updateData = async() => {
    const res = await fetchData();
    let b = res.filter((list: ICity) => list.city.includes(keyword) === true)
                .slice(0,10);
    // console.log(b);
    setKeyItems(b);
  }
  useEffect(() => {
    updateData();
    },[keyword])
    return (
    <SearchContainer>
     <Search value={keyword} onChange={onChangeData} />
      <img src="assets/imgs/search.svg" alt="searchIcon" />
       {keyItems.length > 0 && keyword && (
        <AutoSearchContainer>
         <AutoSearchWrap ref={autoRef}>
          {keyItems.map((search, idx) => (
           <AutoSearchData
            key={search.city}
            onClick={() => {
            setKeyword(search.city);
           }}
            >
            <a href="#">{search.city}</a>
            <img src="assets/imgs/north_west.svg" alt="arrowIcon" />
           </AutoSearchData>
          ))}
         </AutoSearchWrap>
        </AutoSearchContainer>
       )}
      </SearchContainer>
     );
}
export default Header;

마무리

이번 포스팅을 진행하면서, 이전 포스팅에서 설명하지못한 부분들이 있습니다.(useState,useEffect,interface)등등..
기회가 된다면 정리해서 올리도록 하겠습니다.
+ 다음 포스팅 예고
자동완성 검색창이 나타났을때, 키보드 방향키를 통해 접근할 수 있도록 하는 포스팅을 업로드하겠습니다.

감사합니다

좋은 웹페이지 즐겨찾기