본문 바로가기

카테고리 없음

[동물 정보 사이트] 3. 동물 이름으로 검색기능 만들기 & 검색 결과 페이지 생성하기

1. 검색 및 결과를 보여주는 Search 컴포넌트 생성하기

export default function Search() {
  // inputValue: 입력 필드의 현재 값을 저장하는 상태 변수
  const [inputValue, setInputValue] = useState("");

  const handleSearch = (e) => {
    e.preventDefault();
  };

  return (
    <Wrapper>
      <Form onSubmit={handleSearch}>
        <input
          onChange={(e) => setInputValue(e.target.value)}
          value={inputValue}
          placeholder="동물 이름 검색"
        />
        <button type="submit">검색</button>
      </Form>
    </Wrapper>
  );
}

 

사용자가 입력한 값을 onChange 이벤트를 통해 상태(`inputValue`)로 관리하고, 검색 버튼을 누르면 새로고침을 발생시키지 않는 `handleSearch`이벤트 핸들러를 생성하여 Form 컴포넌트에 등록하였다.

 

2. useSearchParams로 검색 결과를 URL에 동적으로 업데이트 하기

export default function Search() {
  const [inputValue, setInputValue] = useState("");
  // searchParams : 현재 URL의 쿼리 파라미터를 읽기 위한 객체
  const [searchParams, setSearchParams] = useSearchParams();

  const handleSearch = (e) => {
    e.preventDefault();
    if (inputValue.trim()) {
      setSearchParams({ name: inputValue });
    } else {
      setSearchParams({});
    }
    setInputValue("");
  };

  return (
    <Wrapper>
	...
    </Wrapper>
  );
}

 

`useSearchParams`는 `handleSearch`함수에서 사용자가 입력한 검색어를 URL 쿼리 파라미터로 설정하고, 검색 결과를 반영할 수 있도록 URL을 동적으로 업데이트하는 기능을 한다. 즉, 여기서는 `name`이 쿼리 파라미터의 키, `inputValue`는 값이 되어 URL에 반영된다.

 

3. 검색어로 데이터를 필터링하기

export default function Search() {
  const [inputValue, setInputValue] = useState("");
  const [searchParams, setSearchParams] = useSearchParams();
  // filteredData: 검색된 데이터를 저장하는 상태 변수(초기엔 전체 데이터)
  const [filteredData, setFilteredData] = useState(data);

  useEffect(() => {
    const query = searchParams.get("name");
    if (query) {
      const filtered = data.filter((animal) => animal.name.includes(query));
      setFilteredData(filtered);
    } else {
      setFilteredData(data);
    }
  }, [searchParams]);

  const handleSearch = (e) => {
    e.preventDefault();
    if (inputValue.trim()) {
      setSearchParams({ name: inputValue });
    } else {
      setSearchParams({});
    }
    setInputValue("");
  };

  return (
    <Wrapper>
	...
    </Wrapper>
  );
}

 

`useEffect`를 사용함으로써 ①처음 컴포넌트가 렌더링될 때와 ②URL의 `searchParams`가 변경될 때마다 검색어를 가져와서 데이터를 필터링할 수 있게 하였다. `searchParams.get("name")`을 통해 쿼리 파라미터에서 `name`값을 추출하고, 그 값이 있을 경우 동물 데이터를 필터링해서 `filteredData`에 저장하였다.

 

4. 검색 결과를 화면에 표시하기

export default function Search() {
...

  return (
    <Wrapper>
      <Form onSubmit={handleSearch}>
        <input
          onChange={(e) => setInputValue(e.target.value)}
          value={inputValue}
          placeholder="동물 이름 검색"
        />
        <button type="submit">검색</button>
      </Form>
      <Results>
        {filteredData.length > 0 ? (
          filteredData.map((el) => {
            const { id, name, img, description } = el;
            return (
              <Link to={`/detail/${id}`} key={id}>
                <Card name={name} img={img} description={description} />
              </Link>
            );
          })
        ) : (
          <Message>데이터가 없습니다!😔</Message>
        )}
      </Results>
    </Wrapper>
  );
}

 

사용자가 검색을 완료하면, 해당 검색어에 맞게 필터링된 데이터(`filteredData`)를 `map`고차함수를 이용하여 화면에 출력하였다. 초기 Search 화면에는 모든 데이터를 렌더링하지만, 검색 후에는 `filteredData.length > 0` 조건을 사용해 필터링된 데이터가 없을 경우에는 '데이터가 없습니다" 라는 메시지가 출력되도록 설정하였다.

 

그리고 검색 시, 같은 형식의 동물 카드가 렌더링 될 수 있도록 Card 컴포넌트를 별도로 추출하여 Main 페이지와 Search 페이지에서 재사용하였다.

어떻게 새로고침을 해도 검색 결과가 유지될 수 있을까?
혹시 React Router에 '캐싱' 기능이 있는 걸까 했지만, 결과부터 말하자면 '아니다'. URL 쿼리 파라미터는 브라우저에 의해 관리되는 상태다. 쿼리 파라미터는 URL에 포함되므로, 페이지를 새로고침하거나 다른 페이지로 이동했다가 돌아와도 URL에 쿼리 파라미터가 그대로 남아있기 때문에 

 

5. (추가) useNavigate를 이용하여 홈 화면 / 이전 화면으로 돌아가기

Search 페이지에서 검색한 동물의 카드를 클릭하면 Detail 페이지로 이동할 수 있도록 컴포넌트를 서로 연결하였다. Detail 페이지에서 정보를 확인한 후, 다시 Search 컴포넌트를  새로 접근하는 것이 아니라 '뒤로 가기' 기능을 구현하기 위해 `useNavigate`을 사용하였다.

export default function Detail() {
  const { id } = useParams();
  const navigate = useNavigate();

  const animal = data.filter((el) => el.id === Number(id));
  const [{ name, img, description }] = animal;

  const goBack = () => {
    navigate(-1); // -1은 한 단계 이전 페이지로 이동함을 의미
  };

  return (
    <Wrapper>
      <div>
        <img src={img} alt={name} />
        <p>이름 : {name}</p>
        <p>설명 : {description}</p>
      </div>
      <BackButton onClick={goBack}>이전 화면으로</BackButton>
    </Wrapper>
  );
}

 

Link 컴포넌트와 useNavigate
Link 컴포넌트는 명시적으로 페이지를 이동할 때 사용하기 적합한 선언적인 컴포넌트다. 예제 코드에서는 모든 페이지에서 렌더링되는 Header 컴포넌트에 '홈' 버튼과 '검색' 버튼을 Link 컴포넌트로 구현하였다.

반면, useNavigate는 명령적으로 페이지를 이동해야 할 때 유용하다. 페이지를 이동하기 전에 특정 작업을 수행해야 하거나, 조건에 따라 페이지 이동을 결정해야 할 때 사용한다. 예제 코드에서는 브라우저의 '뒤로 가기' 동작을 구현하기 위해서 Detail 페이지 하단에 '이전 화면으로' 버튼을 useNavigate로 구현하였다.

 

 

6. 최종 코드

https://github.com/kosmosticlay/oz_coding_bootcamp/tree/master/assignments/React/241016_challenge_01