패스트캠퍼스 데브캠프 42일차 [React, 메모장]

Su Min·2024년 7월 18일
0
post-thumbnail

🔗 검색 기능

메모장에 CustomHook과 useEffect사용해서 검색 기능 추가하기

전날 벨로그에 있던 결과 이미지를 보면 메모장 상단에 검색창이 있었지만 해당 코드에는 검색 기능이 없었어서 검색기능까지 추가해보았다.

Header.js

먼저 검색창 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 />
    </>
  )
}

App.js

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}
/>

List.js

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로 마운팅을 관리해보자!

useSearch.js

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

index.js

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}
/>

index.js 최종

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

🔗 결과

profile
성장하는 과정에서 성취감을 통해 희열을 느낍니다⚡️

0개의 댓글

관련 채용 정보