검색 기능 구현 방식에 대해 여러 가지 방식을 고민하게 되었다.
검색 키워드마다 GET 요청을 보내면 불필요한 서버 통신이 많지 않을까 해서 1번 방식을 사용했다.
하지만 검색 키워드 데이터가 수만 개가 된다고 가정하면 한번에 수만 개의 데이터를 GET 요청으로 불러와서 그 안에서 검색하는 것보다, 계속해서 입력한 검색어 GET 요청을 보내는게 서버에 덜 부담될 수 있다는 피드백을 받았다.
그래서 다음엔 검색 입력 이벤트를 lodash로 관리하고 GET 요청을 보내봐야겠다는 생각이 들었다.
const SearchBar = () => {
const [searchText, setSearchText] = useState("");
const [dataList, setDataList] = useState([]);
const [contentNum, setContentNum] = useState(0);
const { posts, hasMore, keyword } = useSelector((state) => state.posts);
const dispatch = useDispatch();
useEffect(() => {
fetchData();
}, [dispatch, posts]);
const fetchData = async () => {
const res = await axios({
method: "get",
url: `${BASE_URL}/api/articles/hashtag`,
headers: {
"Content-Type": "application/json",
Authorization: getCookie("mycookie"),
},
});
setDataList(res.data);
};
const handleChange = (e) => {
setSearchText(e.target.value);
setContentNum(0);
dataList.map((data) => {
const text = data.hashtagList.toString();
// 영문 대소문자 구분 없이 검색 결과 조회
if (text.toLowerCase().includes(e.target.value.toLowerCase())) {
// 해시태그 목록에 내가 입력한 검색어가 있으면 해시태그 게시글 개수 카운트 증가
setContentNum((contentNum) => contentNum + 1);
}
});
e.target.value === "" && __getPosts(1);
};
const handleClick = () => {
dispatch(__getHashtagPost(searchText));
setContentNum(0);
setSearchText("");
};
return (
<StSearch>
<Input
variant="header"
text="검색"
value={searchText}
onChangeHandler={handleChange}
/>
{searchText ? (
<StResult onClick={handleClick}>
<StHashtag>
<BsHash size="20px" />
</StHashtag>
<StText>
<StSearchText>{`#${searchText}`}</StSearchText>
<StNum>{`게시물 ${contentNum}`}</StNum>
</StText>
</StResult>
) : null}
</StSearch>
);
};
export default SearchBar;
[#여행일기 #여행스타그램]
이런식으로 같은 키워드('여행')가 n번 중복될 경우 서버로부터 같은 게시글이 n번 응답되어 들어오는 문제가 있었다.postSlice.jsx
export const __getHashtagPost = createAsyncThunk(
"getHashtagPost",
async (payload, thunkAPI) => {
try {
const response = await axios({
method: "get",
url: `${BASE_URL}/api/articles/search?hashtag=${payload}`,
headers: {
"Content-Type": "application/json",
Authorization: getCookie("mycookie"),
},
});
return thunkAPI.fulfillWithValue({
data: response.data,
keyword: payload,
});
} catch (error) {
return thunkAPI.rejectWithValue(error);
}
}
);
// ...
[__getHashtagPost.fulfilled]: (state, { payload }) => {
state.isLoading = false;
state.posts = payload.data.filter((val, index, arr) => {
const jsonArr = arr.map((val) => JSON.stringify(val));
return jsonArr.indexOf(JSON.stringify(val)) === index;
});
state.keyword = payload.keyword;
},