Original article: How to Build a Search Filter using React and React Hooks

API에 GET 요청을 보내면 서버로부터 응답 데이터를 받습니다. 하지만 가끔은 이 데이터를 관리하는 게 문제가 되기도 합니다.

이 글에서는 React에서 검색 필터를 생성하는 법을 알려 드리려고 합니다. 함수형 컴포넌트와 React 훅을 이용하여 데이터에 있는 특정 단어를 검색할 것입니다.

API에 GET 요청을 보내는 법

우선 서버에서 데이터를 불러오는 API에 GET 요청을 만들어 보겠습니다. 이 데이터를 불러오기 위해 아무 서버를 사용해도 되지만 이 글에서 필자는 사용자 목록을 불러오기 위해 {JSON} placeholder를 사용할 것입니다.

이 예시에서 사용자들의 이름과 이메일을 보여주는 카드들이 있습니다. 또한 특정 사용자들 검색하기 위해 사용할 검색 입력 창이 있습니다.

import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { Card, Input } from 'semantic-ui-react'
export default function Posts() {
    const [APIData, setAPIData] = useState([])
    useEffect(() => {
        axios.get(`https://jsonplaceholder.typicode.com/users`)
            .then((response) => {
                setAPIData(response.data);
            })
    }, [])
    return (
        <div style={{ padding: 20 }}>
            <Input icon='search'
                placeholder='Search...'
            />
            <Card.Group itemsPerRow={3} style={{ marginTop: 20 }}>
                {APIData.map((item) => {
                    return (
                        <Card>
                            <Card.Content>
                                <Card.Header>{item.name}</Card.Header>
                                <Card.Description>
                                    {item.email}
                                </Card.Description>
                            </Card.Content>
                        </Card>
                    )
                })}
            </Card.Group>
        </div>
    )
}

화면 상위의 검색 입력과 사용자 목록 얻기

그리고 만약 React에서 GET API 호출을 다루는 법을 모르신다면 API 호출에 대해 설명하는 React CRUD 작업에 관한 제 블로그 혹은 비디오를 보시길 바랍니다.

검색 입력 박스로부터 검색 입력을 얻는 법

이제 검색 입력 박스로 부터 검색 쿼리(query)를 가져와 보겠습니다.

검색 입력을 위한 state를 생성합니다.

const [searchInput, setSearchInput] = useState('');

여기 searchInput은 문자열이고 검색 입력 값 설정을 위해 setSearchInput을 사용할 것입니다.

이제 검색 기능을 처리할 함수를 만들어 보겠습니다.

const searchItems = () => {
        
}

그리고 이 함수를 onChange 이벤트로 검색 입력에 바인드(bind)합니다.

<Input icon='search'
    placeholder='Search...'
    onChange={() => searchItems()}
/>

이리하여 입력 필드에 어떤 값이든 입력할 때마다 searchItems가 실행이 될 것입니다.

이제 searchItems에 입력 값을 전달해야 합니다.

<Input icon='search'
    placeholder='Search...'
    onChange={(e) => searchItems(e.target.value)}
/>

다음 검색 쿼리를 searchItems 함수에서 전달 받고 이전에 생성한 setSearchInput을 사용하여 searchInput state를 이 전달 받은 값으로 설정하겠습니다.

const searchItems = (searchValue) => {
    setSearchInput(searchValue)
}

콘솔로 searchValue 값을 확인함으로써 위 로직이 잘 실행되는지 확인할 수 있습니다.

Screenshot-2021-08-14-203750

보시다시피 필자의 이름을 입력했고 콘솔에서 이름이 나타납니다.

검색 결과에 따른 아이템 필터링

이제 filter 메소드를 사용하여 APIData를 필터링(filtering)할 것입니다.

const searchItems = (searchValue) => {
    setSearchInput(searchValue)
    APIData.filter((item) => {
        return Object.values(item).join('').toLowerCase().includes(searchInput.toLowerCase())
    })
}

searchItems 함수에서 서버로부터 얻는 데이터를 포함한 APIData state를 필터링하는 filter 메소드를 사용하고 있다는 것을 알 수 있습니다.

또한 객체 아이템으로부터 값들을 얻기 위해 Object.values를 사용하고 있습니다.

그런 다음 join(' ') 메소드를 사용하여 이 값들을 문자열로 바꾸어 줍니다.

다음 toLowerCase 메소드를 사용하여 이 문자열들을 소문자로 바꾸어 줍니다.

마지막으로 이 문자열들이 검색란에 입력한 값을 포함하고 있는지 확인합니다. 검색 입력 또한 소문자로 바꾸어 줍니다. 이는 단어를 대문자로 입력했을 시 검색을 더 효율적으로 만들어 주기 위해 그 문자열을 소문자로 바꾸게 하는 것입니다.

그런 다음 전체 쿼리를 반환합니다.

이제 이 필터링한 배열을 변수에 저장해야 합니다.

const filteredData = APIData.filter((item) => {
    return Object.values(item).join('').toLowerCase().includes(searchInput.toLowerCase())
})

콘솔을 통해 위에서 만든 기능을 확인해 보겠습니다. 사용자 이름을 검색해보면 해당 사용자 이름에 맞는 데이터를 보게 될 것입니다.

검색 아이템 콘솔에서 확인

이제 필터 데이터를 저장해야 하므로 state를 생성하겠습니다.

const [filteredResults, setFilteredResults] = useState([]);

필터링한 데이터를 담은 state를 생성합니다.

그런 다음 setFilteredResults를 사용하여 searchItems 함수에서 이 state를 filteredData로 설정합니다.

const searchItems = (searchValue) => {
    setSearchInput(searchValue)
    const filteredData = APIData.filter((item) => {
        return Object.values(item).join('').toLowerCase().includes(searchInput.toLowerCase())
    })
    setFilteredResults(filteredData)
}

필터링 결과를 UI에 보여주는 법

이제 이 필터링 결과들을 주 UI에 보여주도록 하겠습니다.

우선 검색 입력란이 비어 있는지 확인하고 비어 있다면 모든 데이터를 보여주는 코드를 작성해야 합니다. 비어 있지 않다면 입력 값에 따라 결과를 필터링할 것입니다.

const searchItems = (searchValue) => {
    setSearchInput(searchValue)
    if (searchInput !== '') {
        const filteredData = APIData.filter((item) => {
            return Object.values(item).join('').toLowerCase().includes(searchInput.toLowerCase())
        })
        setFilteredResults(filteredData)
    }
    else{
        setFilteredResults(APIData)
    }
}

또한 애플리케이션의 반환 부분에서도 똑같은 확인이 필요합니다.

그래서 검색 단어의 길이가 1보다 크면 필터가 된 데이터를 보여줄 것입니다. 1보다 작다면 APIData state에 담긴 모든 데이터를 보여줄 것입니다.

<Card.Group itemsPerRow={3} style={{ marginTop: 20 }}>
    {searchInput.length > 1 ? (
        filteredResults.map((item) => {
            return (
                <Card>
                    <Card.Content>
                        <Card.Header>{item.name}</Card.Header>
                        <Card.Description>
                            {item.email}
                        </Card.Description>
                    </Card.Content>
                </Card>
            )
        })
    ) : (
        APIData.map((item) => {
            return (
                <Card>
                    <Card.Content>
                        <Card.Header>{item.name}</Card.Header>
                        <Card.Description>
                            {item.email}
                        </Card.Description>
                    </Card.Content>
                </Card>
            )
        })
    )}
</Card.Group>

이제 검색 필드에서 사용자 이름을 검색한다면 해당 사용자의 데이터를 얻게 됩니다.

UI에 검색 입력에 대한 결과

검색 창에서 이름을 지우면 모든 데이터를 얻게 됩니다.

검색 입력란이 비어 있을 경우 모든 데이터 출력

여기 참고할 분들을 위해 전체 코드를 적어 두겠습니다.

import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { Card, Input } from 'semantic-ui-react'
export default function Posts() {
    const [APIData, setAPIData] = useState([])
    const [filteredResults, setFilteredResults] = useState([]);
    const [searchInput, setSearchInput] = useState('');
    useEffect(() => {
        axios.get(`https://jsonplaceholder.typicode.com/users`)
            .then((response) => {
                setAPIData(response.data);
            })
    }, [])
    const searchItems = (searchValue) => {
        setSearchInput(searchValue)
        if (searchInput !== '') {
            const filteredData = APIData.filter((item) => {
                return Object.values(item).join('').toLowerCase().includes(searchInput.toLowerCase())
            })
            setFilteredResults(filteredData)
        }
        else{
            setFilteredResults(APIData)
        }
    }
    return (
        <div style={{ padding: 20 }}>
            <Input icon='search'
                placeholder='Search...'
                onChange={(e) => searchItems(e.target.value)}
            />
            <Card.Group itemsPerRow={3} style={{ marginTop: 20 }}>
                {searchInput.length > 1 ? (
                    filteredResults.map((item) => {
                        return (
                            <Card>
                                <Card.Content>
                                    <Card.Header>{item.name}</Card.Header>
                                    <Card.Description>
                                        {item.email}
                                    </Card.Description>
                                </Card.Content>
                            </Card>
                        )
                    })
                ) : (
                    APIData.map((item) => {
                        return (
                            <Card>
                                <Card.Content>
                                    <Card.Header>{item.name}</Card.Header>
                                    <Card.Description>
                                        {item.email}
                                    </Card.Description>
                                </Card.Content>
                            </Card>
                        )
                    })
                )}
            </Card.Group>
        </div>
    )
}

이제 React 훅을 이용하여 React에서 완전 함수형의 검색 필터를 가지게 되었습니다.

보통 이 기능은 API 엔드포인트에서 검색 쿼리 인자를 전달하여 백엔드에서 처리하지만 프론트엔드에서 그 기능을 처리하는 것을 아는 것도 중요합니다.

이 글에 대한 보충 설명이 필요하다면 React와 React 훅을 이용하여 검색 필터 만들기에 관한 제 유투브 비디오를 확인하시면 되겠습니다.

이상 마무리 짓겠습니다. 감사합니다.