[React] localStorage로 최근 본 상품 만들기

Yunhye Park·2024년 4월 7일
0
post-thumbnail
post-custom-banner

상황

클릭한 상품의 title을 배열 형태로 localStorage에 저장하여 최근 본 상품으로 보여주려는데 배열로 담기지 않고, 상품 페이지마다 값이 갱신된다.

  useEffect(() => {
    setFade('end');

    // localStorage에 최근 본 상품 업데이트
    if (!localStorage.getItem('watched')) {
       localStorage.setItem('watched', JSON.stringify([]));
     }
     const getWatchedList = localStorage.getItem('watched');
     const watchedList = JSON.parse(getWatchedList);
    // 새 값 중 중복 제거
     const newWatchedList = watchedList.forEach((element) => {
       if (element === pickProd.id) return watchedList;
       else {
         const newList = watchedList.push(pickProd.id);
         return newList;
       }
     });

    return () => {
      setFade('');
    };
  }, []);

원인 분석

useEffect 내 초기화 부분

메인 페이지에서 localStorage의 key를 초기화하고 디테일 페이지에서 이를 수정하면 된다는 의미로 useEffect를 강사 분이 언급했는데, 나는 Detail 페이지의 useEffect에 모든 내용을 넣는 걸로 오인했다.

그래서 페이지 접속할 때마다 항상 배열이 초기화되었던 것이다.

즉 문제 원인은 useEffect로 인한 초기화였다. 처음엔 이걸 발견하지 못해서 아래와 같은 삽질을 했다.

  • typeof에서의 타입

자료가 제대로 안 담겼나 싶어서 JSON.parse로 localStorage에 있는 key의 typeof을 확인했더니, object였다. 배열이든 디렉터리든 객체인 건 맞기 때문에 당연했는데, 콘솔에 Array가 찍힌 걸 자주 본 기억이 있어선지 디렉터리로 담긴다고 착각했다.

💡 typeof는 원시형과 함수를 제외한 모든 자료형을 object로 출력한다.

typeof 메서드

P.S. 배열로 변환하는 과정이 필요하다고 생각해 Object.values.entries를 활용했지만 여전히 object였다. (당연) 덕분에 디렉터리형 메서드를 다시 읽어보고 좋았다.. 😇

해결

localStorage에 초기값 넣어주는 건 Main 페이지에 해두고,

  // 최근 본 상품 localStorage 초기화(생성)
  useEffect(() => {
    if (!localStorage.getItem('watched')) {
      localStorage.setItem('watched', JSON.stringify([]));
    }
  }, []);

아래처럼 두 방식으로 처리해봤다.

Ver1. Detail 페이지에서 처리

  useEffect(() => {

    // localStorage에 최근 본 상품 업데이트
    let getWatched = JSON.parse(localStorage.getItem('watched'));
    if (!getWatched.includes(pickProd.title)) {
      getWatched.push(pickProd.title);
      localStorage.setItem('watched', JSON.stringify(getWatched));
    }

  }, []);

이 내용을 똑같이 메인 페이지에서 onClick 했을 때도 적용해 보았다.

Ver2. 상품 onClick하며 처리

즉 useEffect로 localStroage에 key를 만들어주고, onClick 했을 때 값을 업데이트 해준다.

하지만 여기서 또 다른 문제를 만났다.

상황

똑같은 로직을 사용했는데 props로 넘겨준 title이 undefined라는 타입 에러가 발생.

        <img
          onClick={() => {
            navigate('/detail/' + props.i);
            let getWatched = JSON.parse(localStorage.getItem('watched'));
            const title = props.product.title;
            console.log(title); // 있음
            console.log(getWatched); // 있음
            console.log(!getWatched.includes(title)); // 있음
            if (!getWatched.includes(title)) { // 오류!
              getWatched.push(props.product.title);
              localStorage.setItem('watched', JSON.stringify(getWatched));
            }

          }}
          className="subImg"
          alt="img"
          src={`https://codingapple1.github.io/shop/shoes${props.i + 1}.jpg`}
        />

원인 분석

콘솔에 찍어보니 props로 넘어온 모든 것들은 물론 if 조건식에 들어가는 것까지 제대로 보인다. 심지어 저 로직은 Detail 페이지에서 사용한 것과 동일해서 문제가 될 수 없다.

💡 혹시 props로 받아오는 시점이 localStorage에 접근한 타이밍보다 더 빨라서 그런 걸까?

setTiemout으로 비동기 처리를 해봤더니 정상 동작한다!

문제 해결, 하지만…

setTimeout을 주석 처리하고 다시 실행해보니 갑자기 된다. (???)
localStorage를 지우고, 서버를 껐다 다시 켜고 실행해도 마찬가지다. 시점의 문제가 아니었던 거면 그냥 컴퓨터 문제인가.. 뭘까..

VScode 자체의 오류로 갑자기 안 되던 게 된다는 이야기를 들어봤는데 이거랑 비슷한 상황인지도 모르겠다. 에디터 업데이트를 안 해서 오류가 생겼던 건가 싶어서 업데이트 해뒀다.

정리

typeof 는 원시형과 함수를 제외한 모든 자료형을 object로 반환한다.

자바스크립트는 크게 원시형과 참조형으로 타입을 구분하고, 배열은 후자에 속하니까 타입을 묻는 메서드로서 정확한 표현을 해준 것 같다.

원치 않게 초기값으로 갱신될 때에는 초기화되는 부분을 확인해야 한다.

useEffect 빈 배열 디펜던시에 특정 변수를 빈값으로 설정하는 코드 작성 등이 그 예다.

최적의 코드에 대한 생각

localStorage는 어디에서든 접근할 수 있어서 최적의 방법이랄 게 딱히 없어 보인다. 좀더 규모가 크면 고려할 게 많아지겠지만 말이다. 하지만 내부 데이터였다면 props drilling을 방지하는 걸 기준으로 로직을 짜야 하지 않았을까.

다만 이것도 현재 컴포넌트에선 무용하긴 하다. Main 내부에 Shoes(상품)가 있고, 디테일 컴포넌트는 다른 컴포넌트와 연결하지 않아서다.

이럴 땐 어떤 로직이 더 최적인 걸까? 그 기준을 모르겠지만.. 계속 개발하면서 고민해 봐야겠다.

profile
일단 해보는 편
post-custom-banner

0개의 댓글