개발자일기/리액트 이야기

React로 요리를 찾아주는 기능 구현 (a.k.a 냉장고 털기) - 2 (자동 완성)

뫙뭉 2021. 10. 1. 13:06
반응형

자동완성 기능을 만들어보자!

 

전에 소개한 아이디어의 중요한 기능 중 하나가 자동완성 기능이었다.

여러 식재료의 이름들을 샘플로 기본적으로 주고, 검색창에 문자를 입력할 때마다 일치하는 재료만 보이게 하는 것이 목표이다.

 

내가 하면서 고민해야 될 부분은 다음과 같았다.

- 모든 아이템은 처음에 보여지는 상태여야 함.

- 타자가 한글자씩 입력 될 때마다 일치하는 항목들만 보여야 함.

 

 

시행착오

내가 처음 고민했던 방법은 다음과 같다.

  1. 미리 만들어 놓은 식재료 data를 받아온다.
  2. className에 화면에 보여질 재료들에 active를 부여하여 목록생성
  3. onChange 이벤트를 통해 input에 값이 입력되면
  4. 모든 아이템에 active를 지우고 filter해서 active를 다시 부여하면
  5. 일치 항목만 남아 있을 것이다.

일단 생각나는대로 코드를 짜보자는 생각으로 진행하였던 방법이었고 당연히 보기 좋게 실패하였다.

당시 짰던 코드를 보고 이어서 이야기 해보자.

// Search.jsx
function Seach({itemsData) {

  const autoComplete = (event) => {
      const value = event.target.currentValue;
      const items = document.queryselectorAll('.item');
      items.forEach(item => {
      	if(item.classList.contains(value){
        	items.classList.remove('active');
            item.classList.add('active');
        } else{
        	item.classList.remove('active');
        }
      )
      
      return (
      	<div className='main'>
        	<input className='search_input' type="text" onChange={(e) => autoComplete(e)}/>
            {
            	itemsData.items.map((item) => 
                <li className='item' ...>
                ...
                </li>
            }
        </div>
        )
 }

 

신기하게도 기능적인 면에서는 작동이 되긴 되었다.

input의 value에 따라 element 출력의 변화는 있었으나 가만 들여다 보니 문제점이 보였다. 

이 방법은 지극히 Vanila JS적인 접근이었다는 것이 가장 큰 문제점이다.

 

React에서는 querySelector, getElementBy.. 등 DOM요소를 직접 조작하는 방식은 지양한다.

이유는 Virtual DOM을 핸들링 하는 라이브버리이기 때문에 Virtual DOM에 존재하는 것과 Real DOM에 존재하는 것의 차이가 있는 경우 문제가 발생하게 된다.

관련 게시글은 mingule님의 게시글을 참고하기 바란다. 자세히 설명해 주셨다.

https://mingule.tistory.com/61

 

React에서의 querySelector, 잘 쓰고 있는 걸까?(feat. Ref)

들어가기 Vanilla JS를 사용하면서, DOM을 조작하기 위해 정말 많은 DOM Selector들을 쓰곤 했다(querySelector, getElementById, getElementByClassName 등). 하지만 React를 사용하게 되면서 querySelector를 쓰..

mingule.tistory.com

 

그렇다면 어떻게 자동완성을 만들 수 있을까?

리액트에서는 변수 관리를 state를 이용하여 관라한다. 

하면 훨씬 간단하고 추후 데이터를 관리하기 편한 코드를 작성할 수 있다.

 

해결 방법

함수형 컴포넌트에서의 여러 편의성을 위해 개발된 Hooks 중 useState를 이용하면 더욱 간단하게 기능을 구현할 수 있다. 클래스 명으로 display 여부를 관리하는 것이 아닌 state에 따른 실시간 변수 관리를 통한 방법이며 이는 리액트 동작의 기본이라고 볼 수 있는 부분이다.

// App.jsx
function App({itemData}){ /* itemData는 재료데이터 */
	
  const [data, setData] = useState(itemsData.items)
  const [filteredItem, setFilteredItem] = useState();
  
  const autoComplete = (e) => {
    const currentValue = e.target.value;
    const filtered = data.filter(item => item.name.includes(currentValue)); 
    setFilteredItem(filtered);    /*필터링된 재료를 state에 저장*/
  }
  
  return (
    <div className="App">
      <div className="container">
        <Search          
          autoComplete={autoComplete}
          filteredItem={filteredItem}
        />
      </div>
    </div>
  );
}


// Search.jsx

function Search({autoComplete, filteredItem}){
	<div className="main">
    	<input className='search_input' type="text" onChange={(e) => autoComplete(e)}/>
            <ul className="items-list">
            {
                filteredItem.map((item) =>
                      <li className="item"
                          key={item.id}>
                          {item.name}
                      </li>
            	)
            }
            </ul>
    </div>
}

마찬가지로 input의 currentValue를 받아와 재료들의 데이터의 includes 여부를 판단 후 true에 해당하는 값을 filteredItem state에 저장한다.

이 후 filteredItem.map으로 element를 return하면 className 조작 없이 쉽게 아이템들을 출력할 수 있었다.

아래는 구현 영상.

 

자음 모음까지 쪼개는 건 좀 더 공부가 필요하다는 거시에요

 

 

직접 해보는 것이 성장의 지름길

해당 문제를 해결하고 느낀점은 '역시 직접 해봐야 기억에 남는구나' 이다.

머릿속으로만 아는 것은 진짜 아는 것이 아니라고 했던가. 이번 사건으로 뼈저리게 느낀 것 같다.

가장 기본적인 부분조차도 어찌보면 자만의 늪에 빠져 간과하고 있었던 자신이 조금 부끄럽게 느껴진다.

시작부터 삐끗거리는 것이 불안하기는 하지만 부끄러움 속에서도 지식을 얻었다는 뿌듯함을 느낀 좋은 경험이었던 것 같고 이번 프로젝트를 하면서 많은 것을 배울 수 있겠다라는 기대감에 두근거림을 가슴에 새겨본다.

 

반응형