헤더의 로그인 유무를 나타내기 위해, 로그인 유저의 정보를 관리하는 전역 상태의 필요성을 느끼고 redux를 도입했었는데, 새로고침에도 해당 정보를 유지할 필요성을 느꼈다. 평소의 전역 상태는 일반 상태와 똑같이 새로고침 시에도 사라지는 특성이 있기 때문에 redux-persist를 이용하면 전역 상태를 보존할 수 있기 때문에 이를 사용하기로 했다.
하지만 끊임없는 난관에 봉착해버렸다.
처음에는 빌드에러... 빌드를 고치면 다른에러... 그 에러를 고치면 또 빌드에러... 무려 2일동안 에러의 늪에 빠져버렸다.
다음부터는 이런 에러가 있지 않기를..!!
나를 계속 괴롭혔던 에러는 2가지가 있었다.
1. 에러 사항
redux-persist failed to create sync storage. falling back to noop storage.
- persist를 적용했을 때 가장 처음 직면한 에러다. 클라이언트 사이드에서는 해당 에러가 일어나지 않았지만, 서버 사이드에서 지속적으로 에러가 발생했다.
- redux-persist 라이브러리가 동기화 스토리지를 생성하지 못했을 때 발생하는 오류다.
- 만약 값이 없을 경우를 대비해 미리 noop 스토리지를 만들어 주니 해결이 되었다.
- 어떤 경우에는 Next 버전이 맞지 않아 이런 에러가 발생한다고 하니 버전이 과하게 높거나 낮을 경우 버전을 체크해보자!
next/dynamic 관련 에러
- 여러 접근을 시도하던 중 next/dynamic를 사용하여 클라이언트에서만 PersistGate를 로드하는 방식을 이용하라는 글을 보고, dynamic을 적용 해보았는데, 아래와 같은 에러가 발생했다.
- 해당 에러는 서버 사이드에서 렌더링된 HTML과 클라이언트 사이드에서의 렌더링이 일치하지 않아 발생하는 문제다.
- 찾아본 바로는 서버 사이드에 persistor를 null로 넣으라는 말이 있었는데, 수정해도 별 효과는 없었다.
2. Redux-persist 적용하기
Redux-persist 설치
npm i redux-persist
yarn add redux-persist
import 부분
import { combineReducers, configureStore } from '@reduxjs/toolkit'
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
import { persistReducer, persistStore } from 'redux-persist'
import { WebStorage } from 'redux-persist/es/types'
import createWebStorage from 'redux-persist/lib/storage/createWebStorage'
persistConfig 만들기
const createPersistStorage = (): WebStorage => {
const isServer = typeof window === 'undefined'
if (isServer) {
return {
getItem() {
return Promise.resolve(null)
},
setItem() {
return Promise.resolve()
},
removeItem() {
return Promise.resolve()
},
}
}
return createWebStorage('session')
}
const storage = createPersistStorage()
const persistConfig = {
key: 'root',
storage,
whitelist: ['userInfo', 'searchMain', 'writeRecipe', 'modRecipe'],
}
- createPersistStorage 함수로 상태를 못불러왔을 때의 Noop Storage를 get,set,remove Item을 통해 만들어주고, 불러왔을 때 createWebStorage를 이용해 세션이나 로컬 스토리지에 넣어준다.
- storage를 해당 함수로 만들어 주고, config를 만들어 준다. key값은 'root'로 정했다.
- config에서 whitelist는 persist를 적용할 상태들이며, blacklist로 적용하지 않을 상태들을 넣어줄 수 있다.
Reducer , store에 합치기, middleware 적용하기
const persistedReducer = persistReducer(persistConfig, rootReducer)
const store = configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [
'persist/PERSIST',
'persist/REHYDRATE',
'persist/REGISTER',
'persist/FLUSH',
'persist/PAUSE',
'persist/PURGE',
],
ignoredPaths: ['register', 'rehydrate'],
},
}),
})
const persistor = persistStore(store)
export { store, persistor }
- 적용해줄 reducer들을 persistReducer를 통해 config와 결합해준다.
- reducer를 persistedReducer로 지정해준뒤, middleware에 조건들을 넣어준다.
- ignoredActions / ignoredPaths는 직렬화 가능성 검사를 무시할 액션, 상태 경로의 배열이다. 해당 액션들과 경로가 무시된다.
Provider에 감싸기
'use client'
import { PersistGate } from 'redux-persist/integration/react'
import { store, persistor } from './index'
import { Provider } from 'react-redux'
import React from 'react'
type Props = {
children: React.ReactNode
}
export default function ReduxProvider({ children }: Props) {
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
{children}
</PersistGate>
</Provider>
)
}
- 만들어둔 Provider에 PersistGate를 이용하여 적용시켜 주었다.