👻 Local Storage는 어떻게 설정해줘야할까
https://xionwcfm.tistory.com/313
updateQueryData를 이용하여 이벤트가 발생했을때
서버에 mutation 요청을 보내지않고 클라이언트 측 데이터만 조작하는 것은 성공했습니다.
다만 로컬스토리지가 문제였는데
https://redux-toolkit.js.org/rtk-query/usage/persistence-and-rehydration
https://stackoverflow.com/questions/67943867/what-happens-when-i-use-rtk-query-with-redux-persist
https://github.com/reduxjs/redux-toolkit/issues/1400
이와 관련하여 위와 같은 레퍼런스를 찾아보았지만 대체로
localstorage에 캐시데이터를 저장하는것은 권장하지않고 새로고침을 했을때 사용자가 기대하는것은
새로운 데이터를 불러오는 것이기 때문에 새로고침을 했을때 데이터를 받아오는것이 맞다.라는 요지였습니다.
다만 저는 모든 데이터를 저장하고자하는게 아니라
유지되어야하는게 맞는 정보만 클라이언트에 저장을 해두고 싶었던 것이기 때문에
그리고 redux-persist와 rtk docs에서 제공하는 extractRehydrationInfo 함수와 같은 솔루션들은
사실 예제가 부실한건지 제 머리가 부실한건지 적용하기가 좀 힘이 들었습니다.
🎉문제 해결 방법
간단하게 해결할 방법을 생각해보았는데
전 updateQuery함수가 특정 이벤트가 발생했을때 트리거되도록 코드를 짰습니다.
그리고 특정 이벤트가 발생해서 트리거되면 쿼리데이터를 조작하는 일을 수행합니다.
그렇다면 updateQuery에 로컬스토리지에 관한 로직을 넣으면
로컬스토리지에 북마킹된 요소만 모아두는게 가능할 것 같았습니다.
다만 코드를 작성하던 도중 로컬스토리지의 데이터를 불러오는 과정에서
코드를 귀찮게 작성하게 되는 것을 깨닫게되었습니다.
예컨대 localstorage는 두가지 경우의 수가 있습니다.
localStorage가 비어있을 때 | localStorage가 비어있지 않을 때 |
null | string |
그런데 localStorage에 문자열 이외의 값을 직렬화하여 저장한 경우에는
JSON.parse() 와 같은 함수를 이용하여 이를 파싱해줄 필요성이 있습니다.
그리고 JSON.parse()는 인자로 string 타입만을 받을 수 있기 때문에
localStorage가 비어있을 때를 대비하여 분기처리를 해주거나
as 문법을 이용해 내가 파싱하려는 것이 null타입이 아니라 string임을 타입스크립트에게 알려주어야합니다.
물론 그렇다고해도 로컬스토리지가 비어있는 상태에서는 null 이 반환되는 것도 맞기때문에
as 문법으로 강제하는 것보다는 분기처리를 해주는 것이 나을 것입니다.
따라서 저는 이에 대해 이러한 유틸함수를 작성해서 사용했습니다.
const getLocalStorage = <T>(key: string, initialValue: T): T => {
const getItem = localStorage.getItem(key);
return getItem === null ? initialValue : JSON.parse(getItem);
};
export default getLocalStorage;
키와 이니셜밸류를 인수로 받는 함수입니다.
제네릭을 이용해 이니셜밸류를 여러가지 타입을 받을 수 있도록 설정해주었습니다.
만약 로컬스토리지가 비어있다면 initialValue를 반환시키고
로컬스토리지가 비어있지않다면 localStorage의 데이터를 파싱해서 반환합니다.
다음은 updateQueryData함수를 이용하는 커스텀훅 로직입니다.
import { useDispatch } from 'react-redux';
import { CozApiInterface, cozShoppingAPI } from '../modules/cozShoppingAPI';
import getLocalStorage from '../utils/getLocalStorage';
const useBookmark = () => {
const dispatch = useDispatch();
const patch = cozShoppingAPI.util.updateQueryData;
const bookmarkHanlder = (apiData: CozApiInterface): void =>
dispatch(
patch('getProduct', undefined, (draft: CozApiInterface[]) => {
let localBookMarkData: CozApiInterface[] = getLocalStorage<
CozApiInterface[]
>('bookmark', []);
const index = draft.findIndex(
(data: CozApiInterface) => data.id === apiData.id,
);
if (index === -1) return;
draft[index].bookmark = !draft[index].bookmark;
const bookMarkIndex = localBookMarkData.findIndex(
(data: CozApiInterface) => data.id === apiData.id,
);
if (bookMarkIndex === -1) {
localBookMarkData = localBookMarkData.concat(apiData);
} else {
localBookMarkData.splice(bookMarkIndex, 1);
}
localStorage.bookmark = JSON.stringify(localBookMarkData);
}),
);
return bookmarkHanlder;
};
export default useBookmark;
생각해보니까 useBookmark 함수가 클로저일 필요가 없을 것 같습니다.
코드가 좀 지저분해보이지만 로직 자체는 당연합니다.
localStorage를 아까만들었던 getLocalStorage 함수를 이용해 불러오고
let localBookMarkData: CozApiInterface[] = getLocalStorage<
CozApiInterface[]
>('bookmark', []);
어떤 트리거가 되는 이벤트가 발생하여 bookmarkHandler가 실행되었다고 가정하였을 때
bookmarkHandler에는 제 api의 타입값을 만족하는 객체가 들어옵니다.
그럼 쿼리데이터를 순회하면서 쿼리데이터 안에 인자로 들어온 객체가 있는지 체크합니다.
(그럴일은 없겠지만) 만약 없는 데이터라면 얼리 리턴하고
있는 데이터라면 찾은 인덱스값을 기반으로 bookmark값을 반전시켜줍니다.
그런 뒤 localstorage에 관한 로직을 수행하기 위해 아까 가져온 로컬스토리지 데이터를 순회합니다.
만약 로컬스토리지에 없는 데이터라면 로컬스토리지에 추가시켜주고
로컬스토리지에 있는 데이터라면 로컬스토리지에서 제거해줍니다.
이렇게 코드를 작성하면 로컬스토리지에 들어가있는 아이템들은 북마크가 된 아이템이라는 것을 예상할 수 있게됩니다
그렇다면 이제 미들웨어를 통해 로컬스토리지를 활용해서 초기 데이터를 설정해줄 수 있을 것 같네요
import { Middleware } from '@reduxjs/toolkit';
import { CozApiInterface } from '../cozShoppingAPI';
import getLocalStorage from '../../utils/getLocalStorage';
const addBookmarkProperty: Middleware = () => (next) => (action) => {
const getBookMarkData: CozApiInterface[] = getLocalStorage<CozApiInterface[]>(
'bookmark',
[],
);
if (action.type.endsWith('fulfilled')) {
action.payload = action.payload.map((data: CozApiInterface) => {
const localDataIdx = getBookMarkData.findIndex(
(localData: CozApiInterface) => localData.id === data.id,
);
return localDataIdx === -1
? { ...data, bookmark: false }
: { ...data, bookmark: true };
});
// ({
// ...data,
// bookmark: false,
// })
}
return next(action);
};
export default addBookmarkProperty;
아까 만들어두었던 로컬스토리지 함수를 사용해 로컬스토리지를 가져오고
만약 로컬스토리지에 아이디가 존재하는 쿼리데이터를 만나면
북마크를 true로 설정해주는 로직을 작성했습니다.
☢결론
여러가지를 해보면서 느낀게 동일한 데이터를 여러개의 슬라이스에서 관리하려고하면 어려운 것 같습니다.
성격이 같은 데이터는 하나의 슬라이스에서 관리하는게 가장 좋지 않나 싶어요
미들웨어를 통해 적용하는 방법이 가장 쉬운 방법이었네요..
'redux' 카테고리의 다른 글
조금 힘겨운 CreateAsyncThunk로 Toast UI 구성하기 (0) | 2023.05.16 |
---|---|
RTK Query의 Middleware에 대하여 (0) | 2023.05.14 |
updateQueryData RTKquery로 클라이언트에서만 쿼리 데이터를 조작하고 싶다면 (1) | 2023.05.14 |
예제와 함께 TS ReduxToolkit Query Slow start (1) | 2023.05.01 |
Redux와 Redux-toolkit (0) | 2023.04.15 |