localStorage 문법
localStorage.setItem('데이터이름', '데이터');
localStorage.getItem('데이터이름');
localStorage.removeItem('데이터이름');
그런데 object나 array를 저장하고 싶을 때는 JSON.stringify()함수를 써야 한다. 왜냐하면 localStorage는 텍스트 자료만 저장할 수 있기 때문이다. 이 경우에는 JSON을 이용하자.
localStorage.setItem('todos', JSON.stringify({title: "책 읽기"}));
이렇게 JSON.stringify에 object나 array를 집어넣으면 JSON자료로 바꿔줘서 localStorage에 저장할 수 있다.
그런데 이건 이미 JSON자료로 바꿨기 때문에, 이 localStorage에서 데이터를 가져오려면 다시 오브젝트로 바꿔주어야 한다. 그건 JSON.parse()를 쓰면 된다.
let a = localStorage.getItem('todos');
let b = JSON.parse(a);
이제 간단한 프로그램을 만들어보자.
오류 1 저장은 되었지만 object Object 이런 형태로 저장되었다..
//저장 버튼을 누르면 saveLocal 함수가 실행되고, localStorage에 데이터를 저장한다.
const onSubmit = (e) => {
...
saveLocal();
...
};
const saveLocal = () => {
localStorage.setItem("todoInLocal", todo);
};
오류 2 데이터가 다 저장은 안되었다
const saveLocal = () => {
localStorage.setItem("todoInLocal", JSON.stringify(todo));
};
이렇게 JSON.stringify()를 써서 직렬화시켜준다.
그런데 다른 문제가 생겼다. 나는 4까지 입력하고 저장버튼을 눌렀는데, localStorage에는 3까지만 저장되어 있었다. 아마 비동기적으로 실행돼서 그런것일듯...?하다.
그러면 저 todo가 바뀌자마자 실행되도록 useEffect를 쓰자.
saveLocal함수대신 아래처럼 useEffect로 웹스토리지에 저장하자.
useEffect(() => {
window.localStorage.setItem("todoInLocal", JSON.stringify(todo));
}, [todo]);
전체코드
import React, { useEffect, useState } from "react";
import "./App.css";
import react from "react";
function App() {
const [todo, setTodo] = useState([{ name: "" }]);
let [inputValue, setInputValue] = useState("");
let [newTodo, setNewTodo] = useState({ name: "" });
const inputChg = (e) => {
setInputValue(e.target.value);
};
//inputValue가 변하면, 그때 newTodo값을 바꿔주자
useEffect(() => setNewTodo({ name: inputValue }), [inputValue]);
const onSubmit = (e) => {
e.preventDefault();
setNewTodo({ name: inputValue });
setTodo([...todo, newTodo]);
setInputValue("");
};
const todosMap = todo.map((todoItem, i) => <p key={i}>{todoItem.name}</p>);
//인풋에 todo값들을 입력할 때마다, localStorage에 저장한다.
useEffect(() => {
window.localStorage.setItem("todoInLocal", JSON.stringify(todo));
}, [todo]);
return (
<div className="parent">
name: <div className="todosMap">{todosMap}</div>
<form onSubmit={onSubmit}>
<input value={inputValue} onChange={inputChg}></input>
<button>저장</button>
</form>
</div>
);
}
export default App;
문제는 localStorage에 저장은 잘 되었는데 여전히 새로고침하면 데이터가 그냥 없어졌다는 것이다... 알고보니 그냥 저장만 하는게 아니었다.
로직1 인풋값을 lcoalStorage에 저장한다. (지금 여기까지 한 상태)
로직2 새로고침하면 localStorage에 저장한 것들을 다시 불러온다.
근데 꺼내와야하는건 알겠는데 localStorage.getItem()을 써도... 새로고침하면 데이터가 다 날아갔다. 계속 찾아보고 했는데도 제자리걸음이라 진짜 죽고싶엇다...
그래서 일단 더 단순화시켜서 진행해보기로 했다. 1번까진 객체를 저장했지만 2번부터는 그냥 string을 배열에 저장할 것이다.
(아래 짤처럼 새로고침하면 저장되었던 것도 사라졌다ㅋㅋ^^)
검색하며 알게 된 점은 이렇다.
1. useEffect()를 써서, todo값이 변했을 때.. 그 때 localStorage에 저장하자. (이건 1단계에서 잘 했었음)
useEffect(() => {
localStorage.setItem("todoKey", JSON.stringify(todo));
}, [todo]);
그러니까.. 처음 렌더링이 됐을 때(새로고침을 했을 때)마다 불러와야 하니까 useEffect를 쓴다.
새로고침을 했을 때, 저장된 값이 존재한다면, 그 저장된 값을 (useState인) setTodo에 넣어준다.
useEffect(() => {
const data = localStorage.getItem("todoKey");
if (data) {
setTodo(JSON.parse(data));
}
}, []);
-참고로 setTodo는 useState말하는 것임
const [todo, setTodo] = useState([""]);
근데 문제는 이렇게 했는데도 안된다는 것이었다...
찾다 발견한 곳ㅠ
https://youtu.be/74ThcF5JqzU?t=200
useState를 단순히 ("")이렇게 썼던 부분을 콜백함수로 바꿔주었다.
const [todo, setTodo] = useState("");
const [todo, setTodo] = useState(() => {
if (typeof window !== "undefined") {
const saved = window.localStorage.getItem("todoKey");
if (saved !== null) {
return JSON.parse(saved);
} else {
return [""];
}
}
});
이제 새로고침을 눌러도 잘 남아있다. (짤은 처음부터 무한재생되서 그래욤)
전체코드
import React, { useEffect, useState } from "react";
import "./App.css";
import react from "react";
function App() {
const [inputValue, setInputValue] = useState("");
const [todo, setTodo] = useState(() => {
if (typeof window !== "undefined") {
const saved = window.localStorage.getItem("todoKey");
if (saved !== null) {
return JSON.parse(saved);
} else {
return [""];
}
}
});
const [newTodo, setNewTodo] = useState("");
const inputChg = (e) => {
setInputValue(e.target.value);
};
const onSubmit = (e) => {
e.preventDefault();
setTodo([...todo, newTodo]);
setInputValue("");
};
useEffect(() => {
setNewTodo(inputValue);
}, [inputValue]);
useEffect(() => {
localStorage.setItem("todoKey", JSON.stringify(todo));
}, [todo]);
console.log(todo);
const todosMap = todo.map((item, i) => <li key={i}>{item}</li>);
return (
<div className="parent">
name: <div className="todosMap">{todosMap}</div>
<form onSubmit={onSubmit}>
<input value={inputValue} onChange={inputChg}></input>
<button>저장</button>
</form>
</div>
);
}
export default App;
localStorage.removeItem('키') 메서드를 쓰면 로컬 스토리지에서 해당 키를 가진 값들을 지울 수 있다.
그렇지만, 문제는.. 해당 키를 가진 값들이 모두 지워진다는 것이다. 나는 그 중에서 몇개만 지우고 싶기 때문에 다른 방법을 쓸 것이다.
그러니까, 삭제한다는 개념이 아니라, 클릭한 걸 빼고 새로운 배열을 만든다고 생각하면 된다~!
이 부분은 todo대신 다른 프로젝트를 만들어보았다.
let [daysArr, setDaysArr] = useState([]);
...
//요일 렌더링하기
const daysArrMap = daysArr.map((dayElement, i) => (
<div
className="dayDiv"
key={i}
onClick={() => {
deleteThisDay(dayElement);
}}
>
{dayElement.day}
</div>
));
//클릭하면 요일 삭제
const deleteThisDay = (dayElement) => {
////클릭한 엘리먼트의 id를 비교해서, 같은 id를 가졌으면 배열에 넣지 않는다.
let afterDelete = daysArr.filter((el) => el.id != dayElement.id);
////방금 만든 배열로 기존 배열을 대체한다.
setDaysArr(afterDelete);
};
let [deletedDays, setDeletedDays] = useState([]);
//deletedDays라는 새로운 배열state에 추가한다.
...
const selectDay = (dayElement) => {
setDeletedDays([...deletedDays, dayElement]);
};
...
const completeDaysByBtn = () => {
let daysAfterDelete = [...기존배열].filter(
(day) => !deletedDays.includes(day)
);
};
https://blogpack.tistory.com/969 filter()
4. 최종 배열로 기존 배열을 바꾼다.
const completeDaysByBtn = (e) => {
let daysAfterDelete = [...기존배열].filter(
(day) => !deletedDays.includes(day)
);
//useState로 기존배열 수정한다.
set기존배열(daysAfterDelete);
};
//BuildProfile.js 파일
useEffect(() => {
localStorage.setItem("profilesKey", JSON.stringify(student));
}, [student]);
App.js에서 받아온 진실의 근원인 props! 이 student가 업데이트 될 때마다 localStorage에 저장한다.
원래는 이렇게 그냥 useState에 배열로 넣어놓았었다.
//App.js 파일
const [student, setStudent] = useState([
{
id: "",
name: "",
school: "",
age: "",
wage: "",
onWeek: "",
hour: "",
totalNum: "",
totalWage: "",
firstDate: "",
days: "",
memo: "",
},
]);
이제는 안에 콜백함수를 작성한다. 만약 window가 잘 로드되었고, localStorage에 저장해놓은 게 있으면 그걸 리턴하고, 없으면 value가 ""인 값을 리턴하도록 작성하였다.
//App.js 파일
const [student, setStudent] = useState(() => {
if (typeof window !== "undefined") {
const saved = window.localStorage.getItem("profilesKey");
if (saved !== null) {
return JSON.parse(saved);
} else {
return [
{
id: "",
name: "",
school: "",
age: "",
wage: "",
onWeek: "",
hour: "",
totalNum: "",
totalWage: "",
firstDate: "",
days: "",
memo: "",
},
];
}
}
});
0. 삭제 버튼 만들기
조건부 렌더링으로 deleteState의 상태에 따라 "완료"로 표시되거나 "삭제"로 표시된다.
<div className="profiles__main">
<div>
<div
className="profiles__deleteBtn"
onClick={() => {
onDeleteMode();
}}
>
{deleteState ? <h1>완료</h1> : <h1>삭제</h1>}
</div>
</div>
</div>
let [deleteState, setDeleteState] = useState(false);
const onDeleteMode = () => {
setDeleteState(!deleteState);
};
console.log(deleteState);
deleteState가 true일 땐, 완료를 표시하고 false일 땐 삭제를 표시한다.
return(
{deleteState ? <h1>완료</h1> : <h1>삭제</h1>}
)
const studentInfoMap = student.map((info, i) => (
<div
key={i}
className="profiles__profile"
//엘리먼트를 클릭할 때마다 profileOnClick 실행한다.
onClick={() => {
profileOnClick(info);
}}
>
<p className="profiles__profile__name">{info.name}</p>
</div>
));
let [selectedDays, setSelectedDays] = useState([]);
const profileOnClick = (info) => {
setSelectedDays([...selectedDays, info]);
};
const onDeleteMode = () => {
setDeleteState(!deleteState);
//만약 deleteState가 true라면 (완료버튼을 누른상태) 실행한다.
if (deleteState == true) {
//student는 App.js에서 props로 받아왔고, 학생들의 모든 정보 배열이다.
//기존 student배열 중에서 방금 만든 selectedDays와 겹치는 건 빼고, 새로운 배열을 만든다.
let daysAfterDelete = student.filter(
(info) => !selectedDays.includes(info)
);
//App.js에서 받아온 student를 새로운 배열로 다시 설정해준다.
setStudent(daysAfterDelete);
}
};
useEffect(() => {
localStorage.setItem("profilesKey", JSON.stringify(student));
}, [student]);