검색창 자동완성 만들기 (with: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;
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>
}
결과화면
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;
마무리
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)등등..
기회가 된다면 정리해서 올리도록 하겠습니다.
+ 다음 포스팅 예고
자동완성 검색창이 나타났을때, 키보드 방향키를 통해 접근할 수 있도록 하는 포스팅을 업로드하겠습니다.
감사합니다
Author And Source
이 문제에 관하여(검색창 자동완성 만들기 (with:react)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@gbwlxhd97/검색창-자동완성-만들기-featreact저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)