props-drilling에 이어 context로 props를 전달해서 기존 기능들이 정상적으로 작동되게 만들어보자!!🔥

 

 

 

📌 props-drilling으로 만든 지출 관리 사이트

https://ejunyang.tistory.com/entry/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EA%B0%9C%EC%9D%B8-%EC%A7%80%EC%B6%9C-%EA%B4%80%EB%A6%AC-%EC%82%AC%EC%9D%B4%ED%8A%B8-%EB%A7%B9%EB%93%A4%EA%B8%B0%F0%9F%90%A2

 

[리액트] 개인 지출 관리 사이트 맹들기🐢

세번째 개인 프로젝트이다. 리액트로는 두번째 개인 프로젝트! 간단한 개인 지출 관리 사이트를 만들어보자!!🐢이번 과제는 props-drilling > context > redux 순으로 상태 관리를 변경해가며 구현해야

ejunyang.tistory.com

 

 

컴포넌트

컴포넌트 설명
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]
  );

+ Recent posts