728x90

들아가며...

최근 react와 router을 활용한 검색기능에 대한 질문을 받은 적이 있습니다. 그런데 막상 질문을 받았을 떄, 검색을 하지 않고서는 잘 기억이 나지 않더군요. 실제로 직접 url을 활용한 검색 기능을 구현한 적도 없었고,,,
따라서 간단한 검색 예제를 직접 만들면서 학습했습니다.

 

구현할 검색 서비스

검색 서비스의 경우에는 실제로도 URI 기반으로 작동을 많이 하는데, 이는 타인에게 검색 결과를 공유하기가 수월해 지기 때문입니다. 단순하게 경로를 복사하여 전송해주는 것만으로 사람들은 제가 보고 있는 것과 동일한 페이지를 볼 수 있습니다.

https://github.com/suhong99/StudyRepo/tree/main/search-ex

 

StudyRepo/search-ex at main · suhong99/StudyRepo

Contribute to suhong99/StudyRepo development by creating an account on GitHub.

github.com

예제 소스코드는 위 링크에 있고  UI의 경우 간단하게 챗 gpt를 활용하여 만들었습니다.
예제도 chat gpt를 활용하여 30개의 아티클 제목을 만들었습니다.

 

 

구현 기능은 아래와 같습니다.

 

1. search와 루트에서 공유될 검색창을 만든다.

2. 홈에서는 전체 데이터를 불러온다.

3. 검색창에 내용 입력 후 enter시 search 페이지로 이동한다.

4. search 페이지에서는 입력 내용 기반으로 필터링하여 관련된 아티클만 보여준다.

 

 

해당 기능을 구현하려면 아래의 기능들이 필요합니다.

1. input 창에서 enter 입력 시 입력창의 내용을 URI에 반영하기 

2. 이후 검색 페이지에서는 URI에 있는 퀴리문에서 검색어를 추출하기

3. 추출된 검색어를 바탕으로 데이터를 필터링하기

 

이제 위 순서대로 구현 및 설명을 진행해보려고 합니다.

경로설정은 아래와 같이 하였고, loader를 사용하지 않을 생각이여서 BrowserRouter를 활용하였습니다.

    <Routes>
        <Route element={<Layout />}>
          <Route path="/" Component={Home} />
          <Route path="/search" Component={Search} />
        </Route>
      </Routes>

 

 

 

1. input 창에서 enter 입력 시 입력창의 내용을 URI에 반영하기 

앞서 말했듯이 input 창의 경우에는 Home과 Search에서 공유되어야 합니다. 따라서 Layout에 위치해야합니다.

 

import { useRef } from 'react';
import { Outlet, useNavigate } from 'react-router-dom';

function Layout() {
  const navigate = useNavigate();
  const searchRef = useRef<HTMLInputElement>(null);

  const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
    if (event.key === 'Enter' && searchRef.current) {
      navigate(`/search?query=${encodeURIComponent(searchRef.current.value)}`);
    }
  };

  return (
    <div>
      <header>
        <div> 아티클 검색 </div>
        {`검색창 : `}
        <input
          type="text"
          placeholder="Search..."
          ref={searchRef}
          onKeyDown={handleKeyDown}
        />
      </header>
      <main>
        <Outlet />
      </main>
    </div>
  );
}

export default Layout;

 

enter를 눌렀을 때 URI에 검색창 내용이 반영되면서 이동해야 합니다. 

이때 enter를 칠 때만 검색되게 할 것이므로 input 요소는 리렌더링이 불필요한 요소(uncontrolled)입니다. 따라서 ref 를 통해서 접근해 값을 가져올 것 입니다.

 

이때 페이지 이동에 사용할 함수는 useNavigate 훅의 반환값입니다.

기본적인 사용법은 아래와 같습니다. 이동할 루트를 지정하고, state를 통해서 키와 value를 담을 수 있습니다.

저 같은 경우에는 해당 방법이 아닌  ?(쿼리문) 를 활용하여 상태를 담았습니다.

 

navigate("/new-route", { state: { key: "value" } });


// 동일한 기능
navigate(`/search?query=${encodeURIComponent(searchRef.current.value)}`);
  
navigate('/search', {
    state: { query: encodeURIComponent(searchRef.current.value) },
  });

 

이때 encodeURIComponent는 자바스크립트 내장함수로 URI에 내용을 담을 때 특수문자등이 훼손되지 않도록 인코딩해주는 함수입니다.

 

2. 검색 페이지에서 쿼리문 추출하기 

검색창에 react라고 검색한 경우 아래와 같이 URI가 수정됩니다.

http://localhost:5173/search?query=react

 

이때 Router에서는 useLocation 훅을 활용하여 쿼리문 및 URL을 추출할 수 있습니다.

  const location = useLocation();

  console.log(location, location.search);

location 과 location.search

 

useLocation 훅의 반환값을 보면 pathname에는 URL이 담겨있고, search에 쿼리문이 있음을 확인할 수 있습니다.

이제 location.search에 반환된 쿼리문에서 해당하는 값을 추출해야 합니다.

 

이떄 사용할 수 있는 겂이 URLSearchParams입니다. URLSearchParams 생성자에 쿼리문을 집어넣어서 값을 확인할 수 있습니다.

기본적으로 URI에는 여러개의 쿼리 값을 추가할 수 있습니다. 또한 URLSearchParams는 iterator이기 때문에 반복문으로 추출할 수 있습니다.

  const searchParams = new URLSearchParams(location.search);

  for (const [key, value] of searchParams) {
    console.log(key, value);
  }

 

 

 

저희는 query라는 key값에서만 값을 추출할 것이기 때문에, 위 방법 대신 get 메서드를 통해서 값을 추출하려고 합니다.

  // URL에서 쿼리 파라미터 추출
  const searchParams = new URLSearchParams(location.search);
  const query = searchParams.get('query') || '';

 

3. 검색어를 통해 필터링하기

마지막으로 필터링하는 방법은 아래와 같습니다. 더 복잡하고 효율적인 필터링을 구현할 수도 있겠지만, 이 포스팅에서는 쿼리문 추출에 좀 더 집중하고자 합니다. 제목을 소문자로 바꾼 후, 검색어를 제목에 포함한 아티클만 보여주느 함수입니다.

  const filteredData = DATA.filter((item) =>
    item.title.toLowerCase().includes(query.toLowerCase())
  );

 

 

완성본은 아래와 같습니다.

import React from 'react';
import { useLocation } from 'react-router-dom';
import CardList from '../components/CardList';
import { DATA } from '../dummy/data';

const Search: React.FC = () => {
  const location = useLocation();

  // URL에서 쿼리 파라미터 추출
  const searchParams = new URLSearchParams(location.search);
  const query = searchParams.get('query') || '';


  // 검색어에 따른 필터링
  const filteredData = DATA.filter((item) =>
    item.title.toLowerCase().includes(query.toLowerCase())
  );

  return (
    <div>
      {filteredData.length > 0 ? (
        <CardList list={filteredData} />
      ) : (
        <div>데이터가 없습니다</div>
      )}
      <CardList list={filteredData} />
    </div>
  );
};

export default Search;

 

느낀점...

최근 next 위주의 프로젝트를 하다보니 Router를 사용할 일이 적었습니다.  막상 검색 없이 구현하려니 막막하더군요.
이번 기회에 URLSearchParams와 Router의 훅들에 대해 복습할 기회를 얻어서 좋았습니다.

해당 검색을 간단하게 보완하려면 인풋을 state로 바꾸고  디바운스를 걸어서 약간의 최적화를 한 후에 검색어가 입력시마다 검색 결과가 바뀌도록 할 수 있긴 하지만, 주 관심사가 아니라서 여기서 마치겠습니다.

참고 문헌

 

https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams

 

URLSearchParams - Web APIs | MDN

The URLSearchParams interface defines utility methods to work with the query string of a URL.

developer.mozilla.org

 

https://reactrouter.com/en/main/hooks/use-navigate

 

useNavigate | React Router

 

reactrouter.com

 

https://reactrouter.com/en/main/hooks/use-location

 

useLocation | React Router

 

reactrouter.com

 

+ Recent posts