props-drilling에 이어 context로 props를 전달해서 기존 기능들이 정상적으로 작동되게 만들어보자!!🔥
📌 props-drilling으로 만든 지출 관리 사이트
컴포넌트
컴포넌트 | 설명 |
ExesForm.jsx | 지출내역을 입력하는 컴포넌트이다. 메인 페이지 우측 하단 버튼을 누르면 모달처럼 뜨도록 구현했다. |
MonthList.jsx | 월별 버튼 컴포넌트이다. 해당 월을 누를 때마다 다른 스타일링이 들어간다. |
ExesList.jsx | 월별 버튼을 눌렀을 때 하단에 해당 월의 지출내역이 나타나는 지출 내역 리스트 컴포넌트이다. |
ExesItem.jsx | 각 지출 내역의 컴포넌트이다. |
컨텍스트 생성
📂 context > 📂 Context.jsx
import { createContext } from "react";
export const Context = createContext(null);
App.jsx
최상위 컴포넌트에서 각 컴포넌트에서 사용할 props를 Provider를 사용해 전달해준다. value로 전달해주기 위해선 App.jsx에 데이터들이 있어야한다.
import { useCallback, useEffect, useState } from "react";
import { Context } from "./context/Context";
return (
<Context.Provider
value={{
exes,
setExes,
selectedMonth,
setSelectedMonth,
handleMonthSelect,
onInsert,
filteredList,
}}
>
<Router />
</Context.Provider>
);
};
export default App;
ExesForm.jsx
useContext를 사용해서 사용하고 있던 데이터를 가져온다. 지출내역을 추가하는 컴포넌트로 onInsert 함수를 가져온다.
import React, { useCallback, useContext, useState } from "react";
import { FaPen } from "react-icons/fa6";
import styled from "styled-components";
import { Context } from "../context/Context";
const ExesForm = () => {
const { onInsert } = useContext(Context);
const onSubmit = useCallback(
(e) => {
...
},
[date, item, amount, desc]
);
return (
...
);
};
export default ExesForm;
ExesList.jsx
여기서 살짝 헤맸는데 해당 지출 내역을 가져와서 map메서드로 각 지출 내역을 하나씩 만들어준다. 그래서 ExesItem.jsx에도 useContext를 사용해서 데이터를 가져와야하나? 했는데 사용하기 어려워보였다. 튜터님께 여쭤보니 해당 과제가 context를 사용해보려고 하는것이지 아예 props-drilling을 사용하지 말라는건 아니라고 하셨고, 데이터를 바로 전달할 수 있는 경우에는 props로 하위 컴포넌트에게 넘겨주어도된다고 했다.
import React, { useContext } from "react";
import ExesItem from "./ExesItem";
import { Context } from "../context/Context";
const ExesList = () => {
const { filteredList } = useContext(Context);
return (
<div>
<ul>
{/*
Uncaught TypeError: Cannot read properties of undefined (reading 'map')
커밋 된 후에야 모든 효과를 실행
React는 return에서 map을 반복실행할 때 첫 턴에 데이터가 아직 안들어와도 렌더링이 실행되며
당연히 그 데이터는 undefined로 정의되어 오류
&& 사용
true && expression은 항상 expression으로 실행
*/}
{filteredList &&
filteredList.map((exe) => {
return <ExesItem key={exe.id} exe={exe} />;
})}
</ul>
</div>
);
};
export default ExesList;
MonthList.jsx
import React, { useContext } from "react";
import "../App.css";
import styled from "styled-components";
import { Context } from "../context/Context";
const MonthList = () => {
const { exes, selectedMonth, handleMonthSelect } = useContext(Context);
const monthArray = [1,2,3,4,5,6,7,8,9,10,11,12];
const totalAmount =
exes[selectedMonth] && exes[selectedMonth].length > 0
? exes[selectedMonth].reduce((acc, cur) => {
return (acc += cur.amount);
}, 0)
: 0;
return (
<>
...
</>
);
};
export default MonthList;
Detail.jsx
props-drilling으로 작업할 때 디테일 페이지에 지출내역의 setState를 가져오지 못해서 상태를 업데이트해주지 못했는데 context를 사용하면 setState 가져올 수 있다!
import React, { useCallback, useContext, useEffect, useRef } from "react";
import { useNavigate, useParams } from "react-router-dom";
import styled from "styled-components";
import { IoIosArrowBack } from "react-icons/io";
import { Context } from "../context/Context";
const Detail = () => {
const { id } = useParams();
const navigate = useNavigate();
const { filteredList, setExes } = useContext(Context);
const exe = filteredList.find((item) => item.id === id);
return (
<>
...
</>
);
};
export default Detail;
삭제할 지출 내역을 제외한 새로운 배열을 생성하고, 그 배열을 포함한 새로운 객체를 만든다. 불변성 유지를 위해 전체 월 지출내역 복사, removeExes 안에는 전체 지출 내역 중 삭제된 지출내역을 제외한 나머지 지출내역이 들어가 있다. 로컬스토리지에 해당 데이터를 저장하고 setExes(지출 내역 업데이트)로 상태 업데이트를 해준다.
const onRemove = useCallback(
(id) => {
if (confirm("정말 삭제하시겠습니까?") == true) {
const removeExes = {
...data,
[exe.month]: data[exe.month].filter((ex) => ex.id !== id),
};
localStorage.setItem("expenseList", JSON.stringify(removeExes));
setExes(removeExes);
navigate("/");
} else {
return false;
}
},
[exe]
);
수정 기능도 불변성 유지를 위해 이전 데이터를 복사 후에 수정된 데이터 배열을 월 지출 내역에 업데이트 해준다.
const onModify = useCallback(
(id) => {
const modifiedData = {
id: exe.id,
date: dateRef.current.value, //변수 접근
item: itemRef.current.value,
amount: +amountRef.current.value, //숫자로만 받아야하기때문에 +연산자 사용(NaN 오류 발생)
desc: descRef.current.value,
};
data[exe.month] = data[exe.month].map((ex) =>
ex.id === id ? { ...ex, ...modifiedData } : ex
);
setExes((prevExe) => ({
...prevExe,
[exe.month]: data[exe.month],
}));
localStorage.setItem("expenseList", JSON.stringify(data));
alert("수정이 완료되었습니다.");
},
[exe]
);
'프로젝트' 카테고리의 다른 글
[팀프로젝트] 리액트로 뉴스피드 웹사이트 제작하기 - 사전기획 (0) | 2024.06.13 |
---|---|
[리액트] 개인 지출 관리 사이트 맹들기 - RTK🐢 (0) | 2024.06.10 |
[리액트] 개인 지출 관리 사이트 맹들기🐢 (0) | 2024.05.30 |
[리액트] 리액트로 ToDoList 만들기 - 토글 기능 (0) | 2024.05.20 |
[리액트] 리액트로 ToDoList 만들기 - 삭제 기능 (0) | 2024.05.17 |