React

[React] 찜하기 기능 구현하기 (관심 매물 등록)

코드비버 2025. 2. 11. 00:41

인터넷 쇼핑을 하다보면 소위 말하는 위시리스트, 즉 찜하기 기능을 접할 수 있다.

상품을 바로 구매하지 않더라도 나중에 쉽게 찾아볼 수 있게 해주는 편리한 기능이기 때문에 물건을 판매하는 사이트에는 필수적인 기능이다.

각 매물의 우측 상단 ♡를 누르면 관심 매물에 등록되는 기능을 구현할 것이다.

 

관심 매물을 표시할 컴포넌트 추가 (Interest.jsx)

우선 찜하기 기능은 앞서 포스팅한 검색 기능과 달리 별도의 페이지에서 매물들이 표시되도록 만들었다.

Home.jsx 페이지에서 찜하기 버튼을 클릭하면 Interest.jsx 페이지에서 확인이 가능하게 만들 예정이다.

function App() {
  return (
    <>
      <MockDataContext.Provider value={mockData}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/interest" element={<Interest />} />
        </Routes>
      </MockDataContext.Provider>
    </>
  )
}

export default App

 

Interest.jsx 페이지를 추가하기 위해 최상위 App.jsx 컴포넌트에서 Route path를 추가해주었다.

 

최상위 컴포넌트에서 기능 구현

대략적으로 설명하면 관심있는 매물을 클릭했을 때 해당 매물의 id를 특정 배열에 저장하고, 그 배열을 맵핑해 관심 목록 리스트를 출력할 것이다.

관심 매물의 id를 담을 state는 Home.jsx 컴포넌트와 Interest.jsx 컴포넌트 모두 활용해야하기 때문에 최상위 컴포넌트인 App.jsx 안에 선언한다.

export const MockDataContext=createContext(); // 배열 등 상태를 담을 Context
export const MockDataDispatchContext=createContext(); // 함수를 담을 Context

function App() {
  const [interestId, setInterestId]=useState([]);

  const getInterestId=(id)=>{ // 관심 매물 id를 interestId에 추가
    interestId.includes(id) 
    ? setInterestId(interestId.filter((item)=>item!==id)) // 이미 존재하는 id가 또 들어가면 삭제 (toggle 기능)
    : setInterestId([...interestId, id]);
  }

  const deleteInterestId=(targetId)=>{ // 관심 매물 id를 interestId에서 삭제
    setInterestId(interestId.filter((item)=>item!==targetId));
  }
  return (
    <>
      <MockDataContext.Provider value={{mockData, interestId}}> // 상태를 전달
        <MockDataDispatchContext.Provider value={{getInterestId, deleteInterestId}}> // 함수를 전달
          <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/interest" element={<Interest />} />
          </Routes>
        </MockDataDispatchContext.Provider>
      </MockDataContext.Provider>
    </>
  )
}

 

App.jsx 안에 관심 매물의 id 를 저장할 state를 선언하고 관심 목록에 추가할 getInterestId() 함수와 관심 목록에서 제거할 deleteInterestId() 함수를 각각 선언했다.

 

이제 이 함수들을 Home.jsx 컴포넌트와 Interest.jsx 컴포넌트 모두 사용할 수 있게 Context를 만들어 내보낼 것이다.

함수를 Context로 만들었다는 점을 구분하기 위해 MockDataDispatchContext를 새로 만들어 value에 객체 형태로 전달해준다.

 

관심 매물의 상태를 담는 interestId 배열은 기존에 사용하던 MockDataContext의 value에 추가했다.

 

리스트 컴포넌트에서 기능 구현

import { useContext } from 'react';

const List=({filteredList})=>{
 	const {interestId}=useContext(MockDataContext);
 	const {getInterestId}=useContext(MockDataDispatchContext);
    
	return (
		<ul>
		{filteredList.map((item)=>(<li key={item.id}><button onClick={getInterestId(item.id)}></button></li>))}
 		</ul>
	);
}

 

하위 컴포넌트인 리스트 컴포넌트에선 useContext Hook을 사용해  최상위 컴포넌트에서 구현된 interestId 배열과 getInterestId 함수를 불러왔다.

그 다음 맵핑된 리스트 안에 있는 버튼에 onClick 이벤트로 getInterestId 함수를 실행하고 interestId 배열에 해당 매물의 id를 저장하면 된다.

 

이렇게 두 개의 매물에 찜하기 버튼을 클릭하면 해당 매물의 id가 interestId 배열에 추가된 상태이다.

 

관심 목록 컴포넌트에서 랜더링

import { useState, useContext } from 'react';
import { MockDataContext, MockDataDispatchContext } from '../../App';

const InterestList=()=>{
    const {mockData, interestId}=useContext(MockDataContext); 
    // 전체 매물 목록, 관심 목록 리스트를 만들기 위한 id 배열
    const {deleteInterestId}=useContext(MockDataDispatchContext); 
    // 관심 목록 삭제 함수

    const [interestList, setInterestList]=useState(mockData.filter((item)=>interestId.includes(item.id)))
    // 화면에 렌더링할 관심 목록 리스트
    
    const onClickDelete=(targetId)=>{
        deleteInterestId(targetId); // 관심 목록에서 해당 id 삭제
        setInterestList(interestList.filter((item)=>item.id!==targetId)); // 관심 목록 리스트 재설정
    }
    
    return (
        <ul>
        {interestList.map((item)=>(
            <li key={item.id}>
            	{/*item 정보 markup 생략*/}
                <button onClick={onClickDelete}>삭제</button>
            </li>
        ))}
        </ul>
    );
}

 

관심 매물에 해당하는 목록만 출력해야 하기 때문에 전체 매물(mockData)에서 관심 매물(interestId)에 해당하는 목록만 필터링 하기 위해 새로운 state(interestList)를 만든다.

이제 만들어진 interestList 배열을 맵핑해 관심 매물을 출력하면 된다.

 

상단에 관심 매물로 표시한 두 개의 매물이 출력되는 것을 확인할 수 있다.

관심 매물 리스트를 관리하는 interestList 배열이 하위 컴포넌트에 있지만, 이 또한 최상위 컴포넌트에 있는 interestId 배열을 기준으로 만들어지기 때문에 삭제 버튼을 클릭했을 때 interestId 배열을 변경시킴으로써 찜하기 버튼의 상태까지 관리할 수 있다.

 

관심 매물 페이지에서 첫 번째 매물을 삭제하면

 

검색 결과 리스트 컴포넌트에서도 첫 번째 매물의 찜하기 버튼에 체크가 해제된 것을 확인할 수 있다.

 

 

마지막으로 내가 몇 개의 매물을 찜했는지 바로 확인할 수 있으면 좋을 것 같아서 모든 페이지에 퀵메뉴를 추가했다.

관심 매물의 개수를 interestId.length만큼 출력하고 useNavigate로 해당 페이지로 바로 이동할 수 있게 만들었다.

 

관심 목록 저장

새로고침을 해도 관심 매물 목록이 초기화되지 않도록 하는 방법은 다음 포스팅을 참고하면 된다.

 

https://duski96.tistory.com/9

 

[JavaScript] localStorage 사용하기 (찜하기 기능 개선)

이전 포스팅에서 찜하기 기능을 구현했는데, 브라우저를 껏다 켜면 내가 찜한 목록이 사라지는 문제가 있었다.App.jsx가 렌더링 될 때마다 찜한 목록의 id를 담고있는 state가 빈 배열로 초기화되기

duski96.tistory.com