메모장에 CustomHook과 useEffect사용해서 검색 기능 추가하기
전날 벨로그에 있던 결과 이미지를 보면 메모장 상단에 검색창이 있었지만 해당 코드에는 검색 기능이 없었어서 검색기능까지 추가해보았다.
먼저 검색창 input
의 value에 state값을, onChange핸들러에는 업데이트되는 setState함수를 넣어둔다.
export const Header = ({ addItemModal, handleSearchTerm, searchTerm }) => {
return (
<>
<header>
<div>
<SearchIcon />
<input type="text" value={searchTerm} onChange={handleSearchTerm} placeholder="Search" />
// input데이터 저장을 위해 useState 설정
</div>
<button onClick={() => addItemModal()}>
<AddIcons />
<span>Add</span>
</button>
</header>
<h1>Your notes</h1>
<Outlet />
</>
)
}
1. useState 선언과 searchTerm의 state 업데이트 함수 선언
const [searchTerm, setSearchTerm] = useState("")
const handleSearchTerm = (e) => {
setSearchTerm(e.target.value)
}
2-1. Array.filter()와 includes() 사용하여 notes의 데이터에서 searchTerm이 포함되는 값 객체에 할당
const searchNoteValue = notes.filter(
(note) =>
note.label.includes(searchTerm) ||
note.content.includes(searchTerm) ||
note.title.includes(searchTerm) ||
note.todays.includes(searchTerm)
)
2-2. Array.filter()와 정규표현식을 사용하는 방법도 있다.
const regexp = new RegExp(searchTerm, "gi")
const searchNoteValue = notes.filter(
(note) =>
regexp.test(note.label) ||
regexp.test(note.title) ||
regexp.test(note.content) ||
regexp.test(note.todays)
)
3. 하위 컴포넌트로 객체 전달
<List
notes={notes}
deleteItem={deleteItem}
addItemModal={addItemModal}
searchNoteValue={searchNoteValue}
searchTerm={searchTerm}
/>
notes데이터와 searchNoteValue가 일치하는 note가 생성되도록 수정
export const List = ({ notes, deleteItem, addItemModal, searchNoteValue, searchTerm}) => {
return (
<ul>
{searchTerm === "" // 검색용어가 있을 때와 없을 때로 구분하여 Item 생성
? notes.map((item) => {
return <Item item={item} key={item.id} deleteItem={deleteItem} addItemModal={addItemModal} />
})
: searchNoteValue.map((item) => {
return <Item item={item} key={item.id} deleteItem={deleteItem} addItemModal={addItemModal} />
})}
</ul>
)
}
setSearchTerm(e.target.value)
의 값이 하나씩 찍힐때마다 <Outlet />
의 요소가 리렌더링 되는 것😱
서두에서 언급했듯이 customHook을 만들어 useEffect로 마운팅을 관리해보자!
import React, { useEffect, useState } from "react"
const useSearch = (value, delay) => {
const [searchValue, setSearchValue] = useState(value)
useEffect(() => {
const handler = setTimeout(() => {
setSearchValue(value)
}, delay)
return () => {
clearTimeout(handler) // delay 시간 내에 value가 변경되면 이전 타이머를 취소하고 새 타이머를 설정
}
}, [value, delay]) // value와 delay 값이 바뀔때만 실행되도록 의존성 배열에 추가
return searchValue // delay 시간이 지난 후 업데이트된 searchValue 리턴
}
export default useSearch
1. useSearch 훅을 가져와 새로운 객체(delaySearchValue)로 선언하고 delay값은 0.5초로 설정한다.
Array.filter( )로 할당된 searchNoteValue
를 함수로 감싸고 state업데이트를 위해 새로운 useState생성한다. handleSearchNotes()
는 useEffect로 감싸고 delaySearchValue
값이 변경될 때만 실행되도록 한다.
import useSearch from "./hooks/useSearch"
.
.
.
const [searchNote, setSearchNote] = useState([])
const delaySearchValue = useSearch(searchTerm, 500)
useEffect(() => {
const handleSearchNotes = () => {
const searchNoteValue = notes.filter(
(note) =>
note.label.includes(searchTerm) ||
note.content.includes(searchTerm) ||
note.title.includes(searchTerm) ||
note.todays.includes(searchTerm)
)
setSearchNote(searchNoteValue)
}
handleSearchNotes()
}, [delaySearchValue])
2. 업데이트된 searchNote를 하위 컴포넌트로 전달한다.
<List
notes={notes}
deleteItem={deleteItem}
addItemModal={addItemModal}
searchNote={searchNote}
searchTerm={searchTerm}
/>
import { BrowserRouter as Router, Routes, Route } from "react-router-dom"
import { useEffect, useState } from "react"
import "./App.css"
import { Header } from "./components/Header"
import { List } from "./components/List"
import { AddItem } from "./components/AddItem"
import useSearch from "./hooks/useSearch"
const App = () => {
const date = new Date()
const currentDate = date.toISOString().substring(0, 10)
const [searchNote, setSearchNote] = useState([])
const [searchTerm, setSearchTerm] = useState("")
const [todays, setTodays] = useState(currentDate)
const [addItem, setAddItem] = useState(false)
const [title, setTitle] = useState("")
const [content, setContent] = useState("")
const [label, setLabel] = useState("Memo")
const [notes, setNotes] = useState([
{
id: 1,
label: "Memo",
title: "벨로그 내용",
content: "하루에 공부한 내용 정리하면서 복습하고 업로드하기",
todays: "2024.07.14",
},
])
const delaySearchValue = useSearch(searchTerm, 500)
useEffect(() => {
const handleSearchNotes = () => {
const searchNoteValue = notes.filter(
(note) =>
note.label.includes(searchTerm) ||
note.content.includes(searchTerm) ||
note.title.includes(searchTerm) ||
note.todays.includes(searchTerm)
)
setSearchNote(searchNoteValue)
}
handleSearchNotes()
}, [delaySearchValue])
const handleSearchTerm = (e) => {
setSearchTerm(e.target.value)
}
const handleTitle = (e) => {
setTitle(e.target.value)
}
const handleContent = (e) => {
setContent(e.target.value)
}
const handleLabel = (e) => {
setLabel(e.target.value)
}
const handleToday = () => {
setTodays(currentDate)
}
const deleteItem = (id) => {
let filteredNotes = notes.filter((item) => item.id !== id)
setNotes(filteredNotes)
}
const addItemModal = () => {
addItem ? setAddItem(false) : setAddItem(true)
if (addItem) {
setTitle("")
setContent("")
setLabel("Memo")
}
}
const addNote = (e) => {
e.preventDefault()
if (title !== "" && content !== "") {
const newNote = { id: crypto.randomUUID(), label, title, content, todays }
const newNotes = [...notes, newNote]
setNotes(newNotes)
addItemModal()
}
}
return (
<>
<main>
{addItem ? (
<AddItem
addNote={addNote}
title={title}
content={content}
handleContent={handleContent}
handleTitle={handleTitle}
handleLabel={handleLabel}
addItemModal={addItemModal}
todays={todays}
handleToday={handleToday}
/>
) : null}
<Router>
<Routes>
<Route
path="/"
element={
<Header addItemModal={addItemModal} handleSearchTerm={handleSearchTerm} searchTerm={searchTerm} />
}
>
<Route
index
element={
<List
notes={notes}
deleteItem={deleteItem}
addItemModal={addItemModal}
searchNote={searchNote}
searchTerm={searchTerm}
/>
}
/>
</Route>
</Routes>
</Router>
</main>
</>
)
}
export default App