[Personal Project] 리액트로 가계부 만들기 (2)

liinyeye·2024년 5월 24일
1

Project

목록 보기
11/44
post-thumbnail

⚙️ TIL

  • 월별 지출 연결해주기
  • 디테일 페이지 데이터 연결
  • 디테일 페이지 CRUD 구현

월별 지출 연결

Home 컴포넌트

처음에는 MonthList에서 상태관리를 해줬지만, Form에도 monthFiltered 데이터가 필요하여 Home 컴포넌트에서 useEffect와 useState로 달에 해당하는 데이터를 그려주는데 필요한 상태 관리를 해주는 것으로 수정했다.

const Home = ({ lists, setLists, addList }) => {
  const [monthFiltered, setMonthFiltered] = useState("");
  const [filteredLists, setFilteredLists] = useState([]);
  // 저장된 로컬스토리지 lists 데이터 중에서 선택한 달과 맞는 데이터 가져오기 -> getMonth()

  useEffect(() => {
    const filtered = lists.filter(
      (list) => new Date(list.date).getMonth() === monthFiltered
    );
    setFilteredLists(filtered);
  }, [lists, monthFiltered]);

  return (
    <>
      <header>
        <h1>ACCOUNTING BOOK</h1>
      </header>
      <StMain>
        <Form
          setLists={setLists}
          lists={lists}
          addList={addList}
          monthFiltered={monthFiltered}
        />
        <MonthsList setMonthFiltered={setMonthFiltered} list={lists} />
        <ExpenseListByMonth filteredLists={filteredLists} />
      </StMain>
    </>
  );
};
  • const [filteredLists, setFilteredLists] = useState([]);
    선택된 달에 맞는 정보 리스트가 필요
    -> 달별로 버튼을 클릭했을 때 해당 데이터만 UI에 그려주기

  • const [monthFiltered, setMonthFiltered] = useState("");
    저장된 데이터 중 getMonth()로 달에 해당하는 값을 가져왔을 때, 선택한 달과 일치하는 데이터 상태관리를 해줌으로써 원하는 달의 데이터를 화면에 보여주기 위함.

MonthList 컴포넌트

달별 버튼을 클릭했을 때의 index를 setActiveIndex와 setMonthFiltered에 넣어줌으로써 monthFiltered는 원하는 달의 데이터를 보여줄 수 있도록 한다. 이때, MonthNameList를 map으로 돌면서 원하는 달을 버튼으로 그려주는데 map의 인자로 들어오는 index와 activeIndex가 일치할 경우 style props인 active도 색상을 변경시켜준다.

const MonthNameList = [
  "January",
  "February",
  "March",
  "April",
  "May",
  "June",
  "July",
  "August",
  "September",
  "October",
  "November",
  "December",
];

const MonthsList = ({ setMonthFiltered }) => {
  const [activeIndex, setActiveIndex] = useState(
    parseInt(localStorage.getItem("filteredByMonth")) || null
  );

  // useState의 상태가 0이 아닐 경우 로컬스토리지에서 데이터를 가져와서 setMonthfiltered에 activeIndex 넣어 화면에 그려주기
  useEffect(() => {
    if (activeIndex !== 0) {
      setMonthFiltered(parseInt(localStorage.getItem("filteredByMonth")));
    }
  }, []);

  const handleClick = (index) => {
    setActiveIndex(index);
    setMonthFiltered(index);
    localStorage.setItem("filteredByMonth", index);
  };

  return (
    <StSection>
      {MonthNameList.map((month, index) => {
        return (
          <StMonthBox
            $active={activeIndex === index}
            key={month}
            onClick={() => handleClick(index)}
          >
            {month}
          </StMonthBox>
        );
      })}
    </StSection>
  );
};

ExpenseListByMonth 컴포넌트

filteredLists로 화면에 해당하는 값만 그려주기

const ExpenseListByMonth = ({ filteredLists }) => {

  return (
    <StUl>
      {filteredLists.map((list) => (
        <Expense key={list.id} list={list} />
      ))}
    </StUl>
  );
};

디테일 페이지 CRUD 구현

디테일 페이지 데이터 연결

우선 디테일 페이지는 기존의 데이터를 인풋값에 가져오는 것부터 난관이었는데, <Link state={list}> Link를 사용해서 페이지를 이동할 때 state로 기존의 데이터를 넘겨서 받을 수 있었다.
나는 list에 담긴 값이 모두 필요했기 때문에 list 자체를 모두 전달했지만 state: {test: 'test'} 이런 식으로 특정 key-value만 가져올 수도 있다.

const Expense = ({ list }) => {
  const { id, date, item, amount, description } = list;

  return (
    <Link to={`/detail/${id}`} state={list}>
      <StLi>
        <StLiBox>
          <StLiBoxH3>{date}</StLiBoxH3>
          <StLiBoxP>
            {item} - {description}
          </StLiBoxP>
        </StLiBox>
        <StLiSpan>{`${amount}`}</StLiSpan>
      </StLi>
    </Link>
  );
};

이 데이터는 useLocation()을 사용해서 객체 형태로 받아올 수 있는데, 여기서 사용해줄 값은 state에 들어있는 객체이기 때문에 location.state로 기존의 데이터를 새로운 변수 preData에 할당해줬다.

const location = useLocation();
const prevData = location.state;

디테일 페이지에서 input은 useRef를 통해 비제어 컴포넌트로 관리해줬다. 기존의 값을 넣어줄 때 value로 기본값을 사용하면 값이 읽기전용으로 취급되어 값이 변경되지 않기 때문에 defaultValue 속성을 사용하여, 비제어 컴포넌트로 활용하면서 변경되는 값으로 사용할 수 있도록 했다.

<StDetailLabel htmlFor="detail-date">Date</StDetailLabel>
        <StDetailInput
          type="date"
          id="detail-date"
          defaultValue={prevData.date}
          ref={dateRef}
        />

수정 & 삭제 기능

current.value로 수정한 인풋값을 가져올 때 처음에는 expenseUpdate함수 바깥에서 정의해줘서 값이 계속 undefined가 나오는 오류가 계속 생겼다.

알고보니 useRef는 비제어 컴포넌트이기 때문에 컴포넌트가 리렌더링 되지 않으면 수정된 값이 함수 바깥에서는 적용이 되지 않아 오류가 생겼던 것이었고, 해당 데이터를 함수 안에서 정의해주니 수정된 값이 제대로 반영됐다.

  // 수정되는 값 반영을 위한 useRef 사용
  const dateRef = useRef(null);
  const itemRef = useRef(null);
  const amountRef = useRef(null);
  const descriptionRef = useRef(null);
  const expenseUpdate = () => {
    // 수정 이벤트가 실행될 때 데이터값 가져오기.
    // 안에서 정의해줘야 수정된 데이터 값을 사용할 수 있음.
    const updatedDate = dateRef.current.value;
    const updatedItem = itemRef.current.value;
    const updatedAmount = amountRef.current.value;
    const updatedDescription = descriptionRef.current.value;

    const updatedList = lists.map((list) =>
      list.id === prevData.id
        ? {
            ...list,
            date: updatedDate,
            item: updatedItem,
            amount: Number(updatedAmount),
            description: updatedDescription,
          }
        : list
    );
    console.log("수정완료");
    setLists(updatedList);
    localStorage.setItem("lists", JSON.stringify(updatedList));
  };

  const expenseDelete = () => {
    const deletedList = lists.filter((list) => list.id !== prevData.id);
    if (confirm("정말로 이 항목을 삭제하시겠습니까?")) {
      setLists(deletedList);
      localStorage.setItem("lists", JSON.stringify(deletedList));
      localStorage.getItem("filteredByMonth");
    } else {
      alert("삭제가 취소되었습니다.");
    }
  };

📎 Things to do

  • useRef, useEffect 리액트 훅 헷갈리는 개념 다시 정리
  • Context API, Redux 활용해서 리팩토링

코드 작성은 어떻게든 해도 아직 다른 사람에게 코드를 설명하는 데 있어서 부족한 점이 많다고 느낀다. 아무래도 개념을 정확히 이해를 하지 못한 채 일단 배운 내용을 어떻게든 써서 하고 있는 프로젝트나 과제를 마무리를 하려고 하다보니 생기는 문제라고 생각한다. 오늘 면담에서 이야기했던 내용이기도 하지만 일단 새로 배우는 내용이 많으니 손에 익숙해지고, 다른 프로젝트를 추가적으로 해보면서 모르는 부분 개념만 잘 익히면 되지 않을까 싶다.

profile
웹 프론트엔드 UXUI

0개의 댓글