[24.08.11] 개인프로젝트 3일차 - 올림픽 메달 트래커 만들기

필수 구현 사항들과 내가 생각하기에 처리해둬야하는 추가 사항들을 모두 만든 것 같아 도전 과제 중 남은 것들을 모두 처리해보기로 했다!

 

정렬기준 추가하기 (총 메달 수 순)

ui 만들기

우선 정렬 기준을 선택할 드롭다운을 만들었다.

드롭다운 메뉴가 보이는 상태인지를 관리할 state와 선택되어있는 옵션을 관리할 state를 선언해주었다.

 

const [sortOption, setSortOption] = useState("금은동 우선순위 순");
const [showSortOptionMenu, setShowSortOptionMenu] = useState(false);

<ul
  id="sortingOptions"
  onClick={() => setShowSortOptionMenu(!showSortOptionMenu)}
>
  {sortOption}
  {showSortOptionMenu && (
    <SortOptionMenu
      setSortOption={setSortOption}
      setShowSortOptionMenu={setShowSortOptionMenu}
    />
  )}
</ul>
  • ul을 클릭할 때마다 showSortOptionMenu 를 true/false 전환
  • showSortOptionMenu가 true일 때만 따로 분리한 컴포넌트인 <SortOptionMenu/>가 보이게 함

 

function SortOptionMenu({ setSortOption, setShowSortOptionMenu }) {
  const selectOption = (event) => {
    setSortOption(event.currentTarget.innerText);
    setShowSortOptionMenu(false);
  };
  return (
    <>
      <li onClick={selectOption}>금은동 우선순위 순</li>
      <li onClick={selectOption}>총 메달수 순</li>
    </>
  );
}
  • SortOptionMenu에는 정렬 조건들이 li 태그로 들어있음
  • li 클릭시 정렬 기준을 선택된 태그의 innerText로 변경해주고, 드롭다운 메뉴가 보이지 않게 닫음

 

선택된 옵션에따라 정렬 함수 내부 동작을 변경하기

const sortData = (data) => {
    if (sortOption === "금은동 우선순위 순") {
      data.sort((a, b) => {
        if (+a.gold !== +b.gold) return b.gold - a.gold;
        else if (+a.silver !== +b.silver) return b.silver - a.silver;
        else return b.bronze - a.bronze;
      });
    } else if (sortOption === "총 메달수 순") {
      data.sort((a, b) => {
        return b.total - a.total;
      });
    }
  };

국가 추가/업데이트 버튼 내에서 데이터를 갱신하면서 sortData가 됐었는데, 여기에만 기준을 추가해주니 기준 설정 -> 추가 에는 반영되지만 추가 후 정렬 기준 변경시 적용되지 않음

 

따라서 정렬 메뉴가 선택됐을 때도 다시 medalData state 정렬필요!

 

const selectOption = (event) => {
  setSortOption(event.currentTarget.innerText);
  
  let sortedData = [...medalData];
  sortData(sortedData);
  setMedalData(sortedData);
  
  setShowSortOptionMenu(false);
};

li 태그를 클릭했을 때 호출되는 selectOption에서 선택된 sortOption에따라 다시 정렬하도록 해줌

드롭다운을 통해 정렬 기준을 선택했을 때 실시간으로 등록되긴하지만, 이전에 선택한 기준으로 정렬됨

 

const selectOption = async (event) => {
  await setSortOption(event.currentTarget.innerText);
  
  let sortedData = [...medalData];
  sortData(sortedData);
  await setMedalData(sortedData);
  
  setShowSortOptionMenu(false);
};

state를 변경하는 set~함수들이 비동기적으로 처리된다고 설명을 들었는데, 그때문에 아직 업데이트가 되지 않은 상태에서 정렬을 하는 것 때문일까싶어서 async 함수로 만들어 await을 붙여줌

위와 마찬가지로 동작함

 

const selectOption = (event) => {
  setSortOption(event.currentTarget.innerText);
  sortOption = event.currentTarget.innerText;
  
  let sortedData = [...medalData];
  sortData(sortedData);
  setMedalData(sortedData);
  
  setShowSortOptionMenu(false);
};

set~ 함수들을 확인해보니 Promise를 반환한다는 안내가 없음

그래서 다시 await을 없애고 변경된 옵션을 바로 받아 사용할 수 있도록 sortOption이라는 변수를 새로 만들어 innerText를 할당해줌

 

원하는대로 동작!

 

알아보니 useEffect라는 hook이 있다고 한다. (state가 업데이트됐을 때 실행할 동작을 지정할 때 사용하는 듯 하다.)

새로운 메달 데이터가 등록됐을 때, 다른 정렬 기준을 선택했을 때 모두 선택된 정렬 기준으로 데이터가 재정렬 되어야하므로 두 state가 변경된 상황을 각각 감지할 수 있는지 만들어보기로 했다.

 

useEffect(() => {
    let sortedData = [...medalData];

    const sortData = (data) => {
      if (sortOption === "금은동 우선순위 순") {
        data.sort((a, b) => {
          if (+a.gold !== +b.gold) return b.gold - a.gold;
          else if (+a.silver !== +b.silver) return b.silver - a.silver;
          else return b.bronze - a.bronze;
        });
      } else if (sortOption === "총 메달수 순") {
        data.sort((a, b) => {
          return b.total - a.total;
        });
      }
    };

    sortData(sortedData);
    setMedalData(sortedData);
  }, [sortOption]);

우선 sortOption이 변경될 때마다 다시 medalData를 sortOption에따라 정렬하게 해주었다.

그런 다음에 데이터 입력 후 medalData가 변경될 때마다 추가된 데이터를 정렬해주기 위해 [sortOption, medalData] 를 추가했더니 오류가 엄청나게 발생했다. (Maximum update depth exceeded)

검색해보니 useEffect로 인해 medalData가 재정렬되는데 medalData가 변경될 때를 조건으로 두다보니 무한 업데이트가 되면서 useEffect 의 콜백함수도 무한실행 되는 것이 원인이라고 생각된다.

 

지난 멤버 소개 페이지 제작 때 이벤트가 무한으로 발생하는 것을 막기 위해 이벤트가 발생 가능한 상태인지 조건을 확인하라는 피드백을 받았었는데, 그게 생각나서 활용해보기로 했다.

 

결국 내가 useEffect를 활용해 하고싶은 동작은 medalData를 정렬하는 것이므로 medalData를 의존성 배열에 추가하지는 않고 그 플래그 자체를 state로 선언해주었다.

 

useEffect 내부 동작이 실행되어야하는 경우는 데이터가 추가됐거나, 옵션이 변경됐을때

 

useEffect(() => {
  if (!dataUpdated) return;
  let sortedData = [...medalData];
  const sortData = (data) => {
    if (sortOption === "금은동 우선순위 순") {
      data.sort((a, b) => {
        if (+a.gold !== +b.gold) return b.gold - a.gold;
        else if (+a.silver !== +b.silver) return b.silver - a.silver;
        else return b.bronze - a.bronze;
      });
    } else if (sortOption === "총 메달수 순") {
      data.sort((a, b) => {
        return b.total - a.total;
      });
    }
  };
  sortData(sortedData);
  setMedalData(sortedData);
  setDataUpdated(false);
}, [sortOption, dataUpdated]);

이렇게하고 국가 추가/업데이트 버튼이 클릭될 때 dataUpdated = true로 변경해주니 무한반복도 일어나지 않고, 데이터가 추가될때는 잘 동작하지만 데이터가 추가된 상황이 아닌 옵션이 변경된 상황에서는 동작하지 않음

sortOption이 변경됐다는 state를 추가로 사용해서  있겠지만.. 애초에 state 변경을 감지하는 것이 목적인데 그걸 확인하기위해 여러번 state를 선언하는 것이 맞지 않는 것 같아 .. 아래와같이 정리하기로 했다

1. medalData를 현재 sortOption에 맞게 정렬하는 함수를 선언

2. 새로운 데이터가 입력됐을 때는 useEffect를 사용하지 않고 원래대로 정렬하는데 이 때 1에서 선언한 함수 사용하기

3. sortOption이 변경됐을 때 useEffect로 재정렬

 

 

로컬스토리지에 저장하고 재접속시 기존 데이터 확인할 수 있도록 하기

const [initialLoad, setInitialLoad] = useState(true);

useEffect(() => {
  if (initialLoad) return;
  localStorage.setItem("medalData", JSON.stringify(medalData));
}, [medalData]);

useEffect(() => {
  if (initialLoad && localStorage.getItem("medalData")) {
    let savedData = JSON.parse(localStorage.getItem("medalData"));
    setMedalData(savedData);
    setInitialLoad(false);
  }
});
  • 첫 로드인지 확인할 수 있는 state를 선언
  • 첫 로드이고 로컬스토리지에 데이터가 있는 경우 저장된 값을 받아와 medalData를 설정해줌
  • medalData가 변경될 때 로컬스토리지에 저장함