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

liinyeye·2024년 5월 23일
0

Project

목록 보기
10/44
post-thumbnail

1. 프로젝트 구조 설정

const App = () => {
  return (
  <>
    <GlobalStyle />
      <BrowserRouter>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/detail/:id" element={<Detail />} />
        </Routes>
      </BrowserRouter>
    </>
  );
};

2. Home 컴포넌트

const Home = () => {
  const [lists, setLists] = useState([]);

  // 컴포넌트가 마운트될 때 로컬스토리지에서 값을 가져오기
  useEffect(() => {
    const savedLists = localStorage.getItem("inputs");
    const newLists = savedLists ? JSON.parse(savedLists) : [];
    setLists(newLists);
  }, []);

  // 새로운 리스트를 추가하는 함수
  const addList = (newList) => {
    const updateLists = [newList, ...lists];
    setLists(updateLists);
    localStorage.setItem("inputs", JSON.stringify(updateLists));
  };

  return (
    <>
      <header>
        <h1>ACCOUNTING BOOK</h1>
      </header>
      <main>
        <Form setLists={setLists} lists={lists} addList={addList} />
        <MonthsList />
        <ExpenseList lists={lists} setLists={setLists} />
      </main>
    </>
  );
};

export default Home;

오류

처음에 새로운 리스트를 추가하는 함수를 작성하는 과정에서 setLists 함수 안에서 새로운 값을 넣어준 updatedLists를 만들고 이를 로컬스토리지에 저장 후 updatedLists를 리턴해주는 방식으로 작성했다. 인풋값이 로컬스토리지에 저장이 잘 됐지만 문제는 빈 배열과 함께 저장이 됐다.
일단 setLists를 저장하고, 로컬스토리지에도 따로 새로운 배열을 저장해주는 방식으로 해결했지만 아직 오류에 대한 정확한 이해를 하지 못해 튜터님께 조언을 구할 예정이다.

  const addList = (newList) => {
    setLists((prevLists) => {
       const updatedLists = [...prevLists, newList];
       localStorage.setItem("inputs", JSON.stringify(updatedLists));
       return updatedLists;
     });
  };

3. Form 컴포넌트

이번에는 인풋창이 네 개나 됐기 때문에 useState로 하나하나 관리해주는 것보다 이전에 활용했던 비제어 컴포넌트로 form state를 관리해주는 방법을 택했다.

TextInput 컴포넌트를 따로 만들어 반복되는 부분을 최소화하려했고, map을 활용해서 개별 컴포넌트를 뿌려주는 것도 고려했지만 전달해줘야하는 prop가 많고 내용이 다양해서 컴포넌트 네 개 각각 다른 데이터의 props를 전달하는 방식으로 구현했다.

const Form = ({ setLists, addList }) => {
  // 폼이 제출됐을 때 데이터 저장
  const onSubmitHandler = (event) => {
    event.preventDefault();

    const formData = new FormData(event.target);
    const date = formData.get("date");
    const item = formData.get("item");
    const amount = formData.get("amount");
    const description = formData.get("description");

    const nextList = {
      id: uuid(),
      date,
      item,
      amount: Number(amount),
      description,
    };

    if (!date || !item || !amount || !description) {
      alert("내용을 모두 입력해주세요.");
    } else alert("내용이 입력됐습니다.");

    setLists((prevList) => [nextList, ...prevList]);
    addList(nextList);
    event.target.reset();
  };

  return (
    <form onSubmit={onSubmitHandler}>
      <TextInput type="date" htmlFor="date" name="date" placeholder="date" />
      <TextInput type="text" htmlFor="item" name="item" placeholder="item" />
      <TextInput
        type="number"
        htmlFor="amount"
        name="amount"
        placeholder="amount"
      />
      <TextInput
        type="text"
        htmlFor="description"
        name="description"
        placeholder="description"
      />
      <button type="submit">Save</button>
    </form>
  );
};

4. TextInput 컴포넌트

상위 컴포넌트에서 받아온 props로 들어가야하는 내용을 넣어주고, styled-components를 이용해 인풋창을 감아주는 div 스타일을 구현했다.

import styled from "styled-components";

const TextInput = ({ type, htmlFor, name, placeholder }) => {
  return (
    <Box>
      <label htmlFor={htmlFor}>{name}</label>
      <input type={type} id={htmlFor} name={name} placeholder={placeholder} />
    </Box>
  );
};

export default TextInput;

const Box = styled.div`
  display: flex;
  align-items: center;
  gap: 5px;
`;
profile
웹 프론트엔드 UXUI

0개의 댓글