weather-NextJS 마이그레이션 중 마주친 에러 해결
✅ localStorage를 사용한 이유는, 별도의 데이터베이스를 사용하고 싶지 않았고 프로젝트의 규모도 크지 않아서 localStorage로도 충분히 구현이 가능하다고 판단했다.
일단, 내가 생각한 로직은 아래와 같다.
localStorage
에 저장되어 있는 값을 가져와서 '저장한 도시 목록'을 렌더링 한다.localStorage
에 저장된다.기존의 코드는 아래와 같고, localStorage
에 값이 들어가지만 새로고침 시 모두 초기화되는 에러가 발생했다. 또한, 기능 구현에 초점을 맞추어서 예외를 따로 처리하지 못했다는 아쉬움이 있다.
const [searchTerm, setSearchTerm] = useState<string>(''); // 검색 키워드
const [searchResults, setSearchResults] = useState<any[]>([]); // 키워드 결과
const [selectedCity, setSelectedCity] = useState<any>([]); // 선택한 도시
const [storage, setStorage] = useState<any> ([JSON.parse(localStorage.getItem('selectedCity')!)]); // 기존 localStorage의 값
// 도시 검색 기능
const handleSearch = (e: any) => {
const input = e.target.value.toLowerCase();
const filteredCountries: any[] = countriesData.countries.filter((country) =>
country.country.toLowerCase().startsWith(input)
);
setSearchTerm(input);
if (input === '') setSearchResults([]);
else setSearchResults(filteredCountries);
};
// 도시 선택 시, 해당 도시 추가
const handleClick = (city: any[]) => {
setSelectedCity((prev) => {
const updated = [...prev, city];
const prevCity = JSON.parse(localStorage.getItem('selectedCity')!);
if (prevCity !== null && prevCity !== undefined) prevCity.push(updated);
localStorage.setItem('selectedCity', JSON.stringify(prevCity));
return updated;
});
setSearchTerm('');
};
// 저장된 도시 목록 불러오기
useEffect(() => {
const prevCities: string | null = localStorage.getItem('selectedCity'); // 값이 있는지 확인
if (prevCities) {
setStorage(JSON.parse(prevCities));
}
}, []);
기존 코드의 의도는,
1. 렌더링 시, localStorage
에 저장된 값을 가져와 storage
상태에 저장한다.
2. 도시를 선택했을 때 해당 도시들을 배열(selectedCity
)로 만들어, localStorage
에 저장한다.
기존 코드는 현재 시간 기준 바로 어제 작성했는데, 대체 무슨 의도를 보여주려했는지 잘 모르겠다. 😥
지금보니 정말 엉망인 코드다..
수정된 코드는 아래와 같다.
const [searchTerm, setSearchTerm] = useState<string>(''); // 검색 바
const [searchResults, setSearchResults] = useState<any[]>([]); // 검색 결과
const [storage, setStorage] = useState<any>(); // 저장된 도시 목록
// 도시 검색 기능
const handleSearch = (e: any) => {
const input = e.target.value.toLowerCase();
const filteredCountries: any[] = countriesData.countries.filter((country) =>
country.country.toLowerCase().startsWith(input)
);
setSearchTerm(input);
if (input !== '') setSearchResults(filteredCountries);
};
// 도시 선택 시 해당 도시 추가
const handleClick = (city : any) => {
let curStorage = JSON.parse(localStorage.getItem('selectedCity')!);
setSearchTerm('');
setSearchResults([]);
// 이미 추가된 도시인지 확인
const alreadyExist = curStorage.some((item: any) => item.country === city.country);
if (alreadyExist) return; // 추가된 도시라면 추가하지 않음
// 추가하지 않았을 경우 추가한다.
curStorage.push(city);
localStorage.setItem('selectedCity', JSON.stringify(curStorage));
setStorage(curStorage);
};
// 첫 렌더링 시, 저장된 도시 목록 불러오기
useEffect(() => {
const curStorage = JSON.parse(localStorage.getItem('selectedCity')!);
if (curStorage === null) {
localStorage.setItem('selectedCity', JSON.stringify([]));
return;
}
setStorage(curStorage);
}, []);
// 도시 선택 시, re-render
useEffect(() => {
}, [storage]);
잘 생각해봤을 때, 기존의 selectedCity(state)
는 필요가 없다는 걸 알았다. 기존에는 도시를 선택하면 이를 selectedCity
에 넣어서, 한번에 localStorage
로 넣으려는 의도였던 것 같은데 그러지 않아도 된다.
단순하게, handleClick
의 인자로 들어오는 city
를 처리하기만 하면 된다.
의도대로 로직이 흘러가게 하려면, 먼저 localStorage의 값을 불러와 저장해야 한다.
왜냐하면, localStorage.setItem()
을 하게되면, 이는 기존의 값에 이어 붙이는게 아닌, 기존의 값을 대체한다는 의미이기 때문이다.
그 후, 중복된 도시를 추가하면 안되므로 선택한 도시가 이미 localStorage
에 저장이 되어있는지 확인해야 한다.
이는 some
함수를 사용했다.
some
은 배열 또는 객체에서, 조건에 맞는 찾고자 하는 값이 있는지 없는지의 결과를 true
, false
로 반환해준다.
사용 방법: [배열 또는 객체].some(조건)
이미 저장된 도시가 아니라면, curStorage
에 추가한다.(push()
) 그리고 curStorage
를 localStorage
에 저장하면 된다.(setItem()
)
✅ 이때, localStorage
는 문자열만 저장이 가능하므로 JSON.stringify()
를 사용한다.
만약, localStorage
에 저장된 도시 목록(selectedCity
)이 없다면 null
을 반환할 것이다. 이 null
을 처리하지 않으면, storage(state)
에 null
이 들어가는 참사가 발생한다.
따라서, 예외를 처리해줬다.
curStorage === null
이라면, localStorage에 []을 저장한다.
왜 []
을 저장하냐면, 도시 목록을 배열로 저장하여 이를 map()
을 사용하여 화면에 뿌려주기 위함이다.
도시를 선택했을 때, 선택한 도시가 바로 목록에 나타나도록 하려했다.
처음에 아래와 같은 코드로 작성했을 때, useEffect()
가 무한히 호출됐다.
그야 당연한게, setStorage()
가 실행되면 의존성으로 인해, useEffect()
가 실행되고 다시 setStorage()
가 실행되기 때문이다.
useEffect(() => {
const curStorage = JSON.parse(localStorage.getItem('selectedCity')!);
if (curStorage === null) {
localStorage.setItem('selectedCity', JSON.stringify([]));
return;
}
setStorage(curStorage);
}, [storage]);
그래서 아래의 코드를 추가했다.
useEffect(() => {
}, [storage]);
딱히, 수행해야 하는 코드가 없으므로 빈 {}
로 뒀다.
이렇게 사용해도 되는 코드인지는 확실하지 않아서, 추가적으로 확인을 해볼 것이다.
생각보다 쉽게 구현이 가능할 거라고 생각했는데, 의외로 복병이었다.
어떤 함수를 써서, 기능을 구현해야 하는지를 알고 있는데 막상 구현했을 때 원하는대로 흘러가지 않는다면 그림이나 글로 프로토타입을 작성해보는 것이 좋다.
JavaScript Some Function
JavaScript JSON.parse
JavaScript JSON.stringify
localStorage 조작하기