[리액트] Localstorage 저장 및 삭제하기
결과물
아래 이미지와 같이 월을 눌렀을 때 해당하는 월의 지출 내역을 보여주도록 구현해보려고 한다. 로컬스토리지에도 데이터를 저장하고 삭제하는 것까지가 목표다.
데이터 추가
우선 expenseList 변수명으로 데이터가 있다면 저장된 데이터들을 객체 형태로 바꿔서 불러오고, 없다면 빈 객체를 가져온다. 객체로 불러오는 이유는 월별마다 다른 지출내역이 보여야하기 때문에 월을 key 값으로 value를 해당 월의 지출 내역 리스트로 하기 위함이다.
const initalLocalData = localStorage.getItem("expenseList")
? JSON.parse(localStorage.getItem("expenseList"))
: {};
새로운 객체를 입력받는 값으로 month:selectedMonth 를 넣어주어 추가할 때마다 해당하는 월이 어딘지 확인해준다. 그리고 데이터를 입력받은 후 기존 객체를 풀어 addExes 새로운 객체에 넣어준다.
const onInsert = (date, item, amount, desc) => {
const newExes = {
id: uuidv4(),
date,
item,
amount,
desc,
month: selectedMonth,
};
//객체로 가져오기
//key - 월, value - 지출내역
const addExes = {
...exes,
};
};
해당하는 월에 지출내역이 없다면? 빈배열을 보여줘 그리고 해당하는 월에 새로운 데이터를 배열에 push 해줘! selectedMonth를 배열에 담은 이유는 월마다 다른 지출내역을 배열에 넣어주기 위함이다. push가 가능한 이유도 배열이기 때문!
if (!addExes[selectedMonth]) {
addExes[selectedMonth] = [];
}
addExes[selectedMonth].push(newExes);
데이터를 추가하고 상태를 업데이트해주고, 로컬스토리지에 string 타입으로 저장해준다. 불러올때는 꼭 객체 타입으로 변환해서 가져오자!
setExes(addExes);
localStorage.setItem("expenseList", JSON.stringify(addExes));
해당 월 지출 내역을 담은 변수를 선언해주고 이 변수를 지출 리스트 컴포넌트에 props로 내려준다. 그럼 지출 리스트 컴포넌트에서 해당 데이터를 갖고 map을 돌려 화면에 데이터를 보여준다.
const filteredList = exes[selectedMonth];
ExesList.jsx
지출 내역 리스트를 화면에 뿌려주는 컴포넌트이다. 필러팅된 props를 map으로 돌려준다. 근데 여기서 👾오류 발생.. 오류 메세지는 아래와 같다.. 정의되지 않은 속성은 읽을 수 없다고 한다.. 저기서 대체 왜?.. 데이터들도 배열로 잘 뜨고 있는데 왜 뜰까..
Uncaught TypeError: Cannot read properties of undefined (reading 'map')
{filteredList.map((exe) => {
return <ExesItem key={exe.id} exe={exe} setExes={setExes} />;
})}
👾 오류 이유
렌더링이 화면에 커밋 된 후에 모든 효과를 실행하기 때문이다. 즉 return에서 map을 반복 실행할 때 첫 턴에 데이터가 아직 다 안들어와도 렌더링이 실행되며, 당연히 그 데이터는 undefined로 정의되어 오류가 나는 것이다. 그렇게 한참을 구글링하다가 나랑 같은 오류를 해결한 글을 보았다.
✨ 해결 방법
논리곱연산자 &&를 사용하자! 자바스크립트에서 true && expression은 항상 expression으로 실행되고 false && expression은 항상 false로 실행된다. 따라서 조건이 참이면 && 바로 뒤의 요소가 출력에 나타난다. 거짓이면 무시하고 건너뛴다.
{filteredList && filteredList.map((exe) => {
return <ExesItem key={exe.id} exe={exe} setExes={setExes} />;
})}
여기까지해주면 월별로 들어가는 지출 내역이 잘 저장되는 것을 확인할 수 있다.
데이터 삭제
삭제는 디테일 페이지에서 진행해보자. 각 지출 리스트를 눌렀을 때 해당하는 리스트의 id 값으로 상세페이지로 들어간다. useParams()를 사용해서 파라미터를 가지고온다.
const { id } = useParams();
useNavigate()로 페이지를 이동하면서 기존 객체 상태를 props로 가지고 오고, 디테일 페이지에선 useLocation()으로 사용해줬다. 여태 컴포넌트로 만들어서 부모 -> 자식으로 props를 넘겨주는 방식으로만 했는데 라우터를 배우면서 상세페이지를 만들고, 페이지 간에 props를 전달하고 사용할 수 있다는 점은 이번 프로젝트를 진행하면서 처음 알았다.
const navigate = useNavigate();
const onDetailButtonHandler = (id) => {
navigate(`/detail/${id}`, { state: { exe } });
};
Detail.jsx
const location = useLocation();
const item = location.state.exe;
받은 props를 사용해 삭제 기능을 구현해보자. 내 페이지는 부모-자식 컴포넌트가 아니라 종속성이 없기때문에 로컬스토리지 데이터를 삭제해주도록 했다. data라는 변수에 저장된 로컬스토리지 데이터들을 담아주고 console.log를 찍어주면 데이터는 잘 불러온다.
let data = JSON.parse(localStorage.getItem("expenseList"));
삭제 함수를 만들어 데이터를 삭제해보자. id 값을 받아와 기존 객체 배열에서 id가 일치하지 않는 값들을 모두 removeData 변수에 담아준다. id 값이 같은 데이터를 제외한 나머지 값 = removeData 라고 생각하면 된다. 그리고 할당된 값을 다시 data[item.month]에 다시 담아준다. 이건 삭제된 데이터를 제외한 나머지 데이터를 재할당(갱신)하는 것이라고 생각하면 된다. 삭제된 값을 제외하고 나머지 값을 다시 로컬스토리지에 저장해야하기 때문이다.
const onRemove = (id) => {
const removeData = data[item.month].filter((exe) => exe.id !== id);
data[item.month] = removeData;
};
여기서 filter를 돌릴 때 data[item.month]로 돌리는 이유는 위 로컬스토리지에서 불러온 데이터를 담은 변수 data는 객체로 불러오기 때문이다. filter 메서드는 배열에만 사용할 수 있기때문에 data[item.month]로 해당하는 월의 데이터로 filter를 돌리면 된다.
아래는 콘솔로 찍어본 data와 data[item.month]이다. 객체와 배열 형태로 찍히는 것을 확인할 수 있다.
그렇게 재할당해준 데이터를 다시 로컬스토리지에 저장해주면 삭제하기 완성..! 여기서 navigate(-1) 은 뒤로가기이다. 상세페이지에서 데이터를 삭제하고 새로고침을 하거나 홈으로 나가야 삭제가 됐는지 확인할 수 있었기 때문에 삭제와 동시에 홈으로 이동하게끔 했다.
localStorage.setItem("expenseList", JSON.stringify(data));
navigate(-1);
뒤로가기 버튼도 마찬가지! -1을 해준다 원래는 navigate('/')로 했었는데 이건 덮어씌우는거랑 같다고 해서 대부분 -1을 사용한다고한다.
const onBackButtonHandler = () => {
navigate(-1);
};
삭제 기능까지 완성! 혼자 하는데 처음에 월별로 데이터를 저장하면서 월을 key값으로 객체로 저장을 하려니 잘쓰고 있던 map, filter 메서드들에 오류가 나서 다 백지상태였다. 배열 메서드들인데 내가 객체로 저장해버리니 오류가 나는 수밖에 튜터님한테 많이 물어보고 콘솔에 객체와 배열이 어떻게 다른지 물어보면서 만들어보았다. 아직 복습을 많이 해야할 것 같다.