localStorage에 state 저장하기

Hmm·2021년 12월 29일
4

PWA 만들기

목록 보기
8/8

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. localStorage에 저장하기

오류 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;

2. localStorage에서 데이터 꺼내오기

문제는 localStorage에 저장은 잘 되었는데 여전히 새로고침하면 데이터가 그냥 없어졌다는 것이다... 알고보니 그냥 저장만 하는게 아니었다.
로직1 인풋값을 lcoalStorage에 저장한다. (지금 여기까지 한 상태)
로직2 새로고침하면 localStorage에 저장한 것들을 다시 불러온다.

  • hooks는 if조건문 안에서 쓸 수 없다. 쓰고 싶다면 useEffect()안에 조건문 if를 넣을 것.

근데 꺼내와야하는건 알겠는데 localStorage.getItem()을 써도... 새로고침하면 데이터가 다 날아갔다. 계속 찾아보고 했는데도 제자리걸음이라 진짜 죽고싶엇다...

그래서 일단 더 단순화시켜서 진행해보기로 했다. 1번까진 객체를 저장했지만 2번부터는 그냥 string을 배열에 저장할 것이다.

(아래 짤처럼 새로고침하면 저장되었던 것도 사라졌다ㅋㅋ^^)

검색하며 알게 된 점은 이렇다.
1. useEffect()를 써서, todo값이 변했을 때.. 그 때 localStorage에 저장하자. (이건 1단계에서 잘 했었음)

  useEffect(() => {
    localStorage.setItem("todoKey", JSON.stringify(todo));
  }, [todo]);
  1. 새로고침을 했을 때, 조건에 따라 localStorage에서 가져오자.

그러니까.. 처음 렌더링이 됐을 때(새로고침을 했을 때)마다 불러와야 하니까 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;

3. localStorage에서 데이터 지우기

localStorage.removeItem('키') 메서드를 쓰면 로컬 스토리지에서 해당 키를 가진 값들을 지울 수 있다.

그렇지만, 문제는.. 해당 키를 가진 값들이 모두 지워진다는 것이다. 나는 그 중에서 몇개만 지우고 싶기 때문에 다른 방법을 쓸 것이다.

(1) 클릭할 때마다, 그 엘리먼트를 localstorage에서 지우고 싶다면?

  1. 배열의 모든 값에 각각 id를 설정해 놓는다.
  2. 클릭했을 때, 클릭한 엘리먼트의 id를 받아온다.
  3. 그 id를 가진 값만 빼고 새로운 배열을 만든다. (filter( ) 이용)
  4. 새로운 배열로 기존 배열을 바꾼다.
  5. 바뀐 배열을 로컬에 다시 저장한다. useEffect()를 이용한다.
    useEffect(() => {
    localStorage.setItem("homeworksKey", JSON.stringify(homework), [homework]);
    }); 이런식으로

그러니까, 삭제한다는 개념이 아니라, 클릭한 걸 빼고 새로운 배열을 만든다고 생각하면 된다~!

이 부분은 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);
  };

(2) 선택한 엘리먼트들을 삭제 버튼을 눌러서 한번에 localstorage에서 지우고 싶다면?

  1. 배열의 모든 값에 각각 id를 설정해 놓는다.
  2. 새로운 배열을 만든다. 클릭했을 때, 클릭한 엘리먼트를 그 새로운 배열에 추가한다.
  let [deletedDays, setDeletedDays] = useState([]);
  
  
  //deletedDays라는 새로운 배열state에 추가한다.
  ...
  const selectDay = (dayElement) => {
      setDeletedDays([...deletedDays, dayElement]);
  };
  ...
  
  1. 기존배열에서 새로운 배열을 비교했을 때 일치하지 않는 값으로만 최종 배열을 만든다. (filter( ))이용)
  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);
  };

내 PWA에서 수정하기

1. localStorage에 데이터 저장하기

//BuildProfile.js 파일

  useEffect(() => {
    localStorage.setItem("profilesKey", JSON.stringify(student));
  }, [student]);

App.js에서 받아온 진실의 근원인 props! 이 student가 업데이트 될 때마다 localStorage에 저장한다.

2. 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: "",
          },
        ];
      }
    }
  });

3. localStorage에서 데이터 지우기


0. 삭제 버튼 만들기
조건부 렌더링으로 deleteState의 상태에 따라 "완료"로 표시되거나 "삭제"로 표시된다.

      <div className="profiles__main">
        <div>
          <div
            className="profiles__deleteBtn"
            onClick={() => {
              onDeleteMode();
            }}
          >
            {deleteState ? <h1>완료</h1> : <h1>삭제</h1>}
          </div>
        </div>
      </div>
  1. 삭제 모드 진입
    삭제 버튼을 눌렀을 때, 삭제할 수 있는 모드로 바꾼다. 삭제버튼을 다시 누르면, 완료 버튼으로 바뀌고 삭제 모드를 해제한다. (토글)
  let [deleteState, setDeleteState] = useState(false);
  const onDeleteMode = () => {
    setDeleteState(!deleteState);
  };
  console.log(deleteState);

deleteState가 true일 땐, 완료를 표시하고 false일 땐 삭제를 표시한다.

return(
{deleteState ? <h1>완료</h1> : <h1>삭제</h1>}
)
  1. 삭제할 엘리먼트 선택하기
    참고로 여기서 info는 지금 저장되어 있는 학생들의 정보이다. 위 사진에선 이수민 학생만 있는데, info에는 모든 학생의 이름, 학교, 나이 등 프로필 정보가 모두 담겨 있다.
  • 클릭할 때 함수 실행
  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>
  ));
  • 클릭한 엘리먼트들을 배열state 에 넣는다.
  let [selectedDays, setSelectedDays] = useState([]);

  const profileOnClick = (info) => {
    setSelectedDays([...selectedDays, info]);
  };
  1. 완료 버튼 누르면 삭제하기
  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);
    }
  };
  1. 바뀐 배열로 localStorage에 다시 저장하기
    student에 변화가 있을 때마다 다시 저장한다.
  useEffect(() => {
    localStorage.setItem("profilesKey", JSON.stringify(student));
  }, [student]);

0개의 댓글