React 자동완성 기능

박은정·2022년 2월 9일
5

TIL

목록 보기
40/72
post-thumbnail

태그 기능과 마찬가지로 자동완성기능을 구현하기 위해서는 어떻게 해야할지 알아보기 위해 google창을 먼저 살펴봤습니다.

검색창에 input값을 입력하면 밑에 input값과 유사한 전체 단어을 dropdown 형식으로 보여주는 자동 완성 기능을 구현했습니다.

필요한 state 정리

1. inputValue

input에 들어간 값

  • google에서는 해당 input값으로 시작하는 단어만 보여주지만, 과제사항에서는 해당 값을 포함하는 모든 단어를 보여주기 때문에 input에 입력한 값을 포함한 단어를 dropdown에 보여줍니다
  • string.includes() 메서드 활용
  • 초기 state: 빈문자열 ''

2.isHaveInputValue

입력된 input값이 있는지의 여부

  • 입력된 input값이 있다면 dropdown을 보여주고, 입력된 input값이 없다면 dropdown을 보여주지 않습니다
  • Boolean형태
  • 초기 state: false

3. dropDownList

dropdown에 보여줄 자동완성된 단어목록(리스트)

  • dropdown을 살펴보면 동일한 형태의 컴포넌트가 반복적으로 보여줍니다
  • array.map() 메서드 활용
  • 초기 state: []

4. dropDownItemIndex

내가 선택한 자동완성된 단어 item의 index

  • dropdown에 나타나는 추천단어 리스트 array에서 내가 선택한 단어(요소)를 가리키기 위해 필요합니다.
  • 만약 dropDownList.map() 를 통해 dropdown이 나왔을 때, 해당하는 indexdropDownItemIndex이 동일하면 배경색을 lightgrey로 변경해서 선택되었음을 알려줍니다.

InputBox 영역

  • Input값이 변경될 때마다 inputValue, isHaveInputValue 값 업데이트했습니다.
  • 키보드가 아래,위,enter 눌릴 때 dropdown의 요소를 선택하기 위해 키보드에 버튼을 눌릴 때마다, 아래의 조건에 따라 dropDownItemIndex 값 업데이트했습니다.

조건

  1. isHaveInputValue 이 없다면 dropdown의 영역도 없을 것이기 때문에 이러한 키보드 이벤트를 고려하지 않습니다.

  2. dropDownItemIndex

    • 아래버튼을 눌렀을 때: dropDownList.length - 1 > dropDownItemIndex ~~
    • 위버튼을 눌렀을 때: dropDownItemIndex >= 0
    • Enter버튼을 눌렀을 때(=해당 dropDownItemIndex선택): dropDownItemIndex >= 0
// 조건을 위와 같이 설정한 이유
dropDownList = ['apple', 'banana', 'javascript']
dropDownList.length // 3
beasts.indexOf('apple') // 0
beasts.indexOf('banana') // 1
beasts.indexOf('javascript') // 2
  1. inputValue이 존재할 때만 보여져야 하기 때문에 isHaveInputValue이 true일 때 렌더링되도록 했습니다.
  2. dropDown에 들어갈 데이터는 array형태의 dropDownList state인데, 만약 하나도 없을 경우 빈 배열만 나오기 때문에
  • dropDownList.length === 0 일 때에는 해당하는 단어가 없다고 렌더링했습니다.
  • 하나 이상 있을 때에는 map()메서드를 통해 <DropDownItem> 컴포넌트를 반복적으로 만들었습니다.
  1. <DropDownItem> 을 눌렀을 때 (=클릭했을 때)
  • 해당하는 값으로 inputValue state를 업데이트 하고,
  • 자동완성 단어를 선택되었기 때문에 dropDown 영역을 보여주지 않기 위해 isHaveInputValue state를 false로 변경했습니다.
  1. <DropDownItem> 을 마우스오버했을 때
  • 해당하는 index를 dropDownItemIndex state로 업데이트합니다.

코드

import React, { useEffect, useState } from 'react'
import styled from 'styled-components'
import Title from '../components/Title'

const wholeTextArray = [
  'apple',
  'banana',
  'coding',
  'javascript',
  '원티드',
  '프리온보딩',
  '프론트엔드',
]

const AutoComplete = () => {
  const [inputValue, setInputValue] = useState('')
  const [isHaveInputValue, setIsHaveInputValue] = useState(false)
  const [dropDownList, setDropDownList] = useState(wholeTextArray)
  const [dropDownItemIndex, setDropDownItemIndex] = useState(-1)

  const showDropDownList = () => {
    if (inputValue === '') {
      setIsHaveInputValue(false)
      setDropDownList([])
    } else {
      const choosenTextList = wholeTextArray.filter(textItem =>
        textItem.includes(inputValue)
      )
      setDropDownList(choosenTextList)
    }
  }

  const changeInputValue = event => {
    setInputValue(event.target.value)
    setIsHaveInputValue(true)
  }

  const clickDropDownItem = clickedItem => {
    setInputValue(clickedItem)
    setIsHaveInputValue(false)
  }

  const handleDropDownKey = event => {
    //input에 값이 있을때만 작동
    if (isHaveInputValue) {
      if (
        event.key === 'ArrowDown' &&
        dropDownList.length - 1 > dropDownItemIndex
      ) {
        setDropDownItemIndex(dropDownItemIndex + 1)
      }

      if (event.key === 'ArrowUp' && dropDownItemIndex >= 0)
        setDropDownItemIndex(dropDownItemIndex - 1)
      if (event.key === 'Enter' && dropDownItemIndex >= 0) {
        clickDropDownItem(dropDownList[dropDownItemIndex])
        setDropDownItemIndex(-1)
      }
    }
  }

  useEffect(showDropDownList, [inputValue])

  return (
    <WholeBox>
      <Title text='AutoComplete' />
      <InputBox isHaveInputValue={isHaveInputValue}>
        <Input
          type='text'
          value={inputValue}
          onChange={changeInputValue}
          onKeyUp={handleDropDownKey}
        />
        <DeleteButton onClick={() => setInputValue('')}>&times;</DeleteButton>
      </InputBox>
      {isHaveInputValue && (
        <DropDownBox>
          {dropDownList.length === 0 && (
            <DropDownItem>해당하는 단어가 없습니다</DropDownItem>
          )}
          {dropDownList.map((dropDownItem, dropDownIndex) => {
            return (
              <DropDownItem
                key={dropDownIndex}
                onClick={() => clickDropDownItem(dropDownItem)}
                onMouseOver={() => setDropDownItemIndex(dropDownIndex)}
                className={
                  dropDownItemIndex === dropDownIndex ? 'selected' : ''
                }
              >
                {dropDownItem}
              </DropDownItem>
            )
          })}
        </DropDownBox>
      )}
    </WholeBox>
  )
}

const activeBorderRadius = '16px 16px 0 0'
const inactiveBorderRadius = '16px 16px 16px 16px'

const WholeBox = styled.div`
  padding: 10px;
`

const InputBox = styled.div`
  display: flex;
  flex-direction: row;
  padding: 16px;
  border: 1px solid rgba(0, 0, 0, 0.3);
  border-radius: ${props =>
    props.isHaveInputValue ? activeBorderRadius : inactiveBorderRadius};
  z-index: 3;

  &:focus-within {
    box-shadow: 0 10px 10px rgb(0, 0, 0, 0.3);
  }
`

const Input = styled.input`
  flex: 1 0 0;
  margin: 0;
  padding: 0;
  background-color: transparent;
  border: none;
  outline: none;
  font-size: 16px;
`

const DeleteButton = styled.div`
  cursor: pointer;
`

const DropDownBox = styled.ul`
  display: block;
  margin: 0 auto;
  padding: 8px 0;
  background-color: white;
  border: 1px solid rgba(0, 0, 0, 0.3);
  border-top: none;
  border-radius: 0 0 16px 16px;
  box-shadow: 0 10px 10px rgb(0, 0, 0, 0.3);
  list-style-type: none;
  z-index: 3;
`

const DropDownItem = styled.li`
  padding: 0 16px;

  &.selected {
    background-color: lightgray;
  }
`

export default AutoComplete
profile
새로운 것을 도전하고 노력한다

0개의 댓글