![[Next.js] Zustand persist 사용하기 (feat. Hydration 에러 해결)](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcegsaM%2FbtsIibkjaBe%2FMgmY99gEJkOkjDquhmAO81%2Fimg.png)
시작하며...
현재 프로젝트의 상태 관리 라이브러리로 Zustand 를 사용하기로 했다.
사용하기 간편한 상태 관리 라이브러리 중에서 꾸준히 업데이트되면서 다운로드 수가 많은 Zustand 를 선택하게 되었다.
내용이 좀 빈약하지만 블로그에 간단하게 Zustand 를 사용하는 방법에 대해 글을 작성했었다.
https://jjang-j.tistory.com/57
[React] Zustand 상태 관리 라이브러리 사용 방법
시작하며...React 에서 다른 컴포넌트로 데이터를 전달하기 위해 props 를 사용한다.그러나 React 의 상태인 state 는 자식 컴포넌트한테만 전달할 수 있어 만약 컴포넌트가 많고 잘 분리되어 있을 경
jjang-j.tistory.com
persist 사용하게 된 이유
아래 코드와 같이 Zustand 코드를 작성하였고
import { create } from 'zustand';
import { getCookie, deleteCookie } from '@/utils/cookieUtil';
export type userInfo = {
id: number;
name: string;
};
interface AuthProps {
user: userInfo | null; // 유저 정보
saveUser: (user: userInfo) => void; // 유저 정보 저장
isLogin: boolean; // 로그인 여부
checkLogin: () => void; // 로그인 여부 확인 함수
logout: () => void; // 로그아웃
}
export const useAuthStore = create<AuthProps>((set) => {
return {
user: null,
isLogin: false,
saveUser: (user) => {
set({ user, isLogin: true });
},
checkLogin: () => {
const accessToken = getCookie('accessToken');
if (accessToken) {
set({ isLogin: true });
} else {
set({ user: null, isLogin: false });
}
},
logout: () => {
deleteCookie('accessToken');
set({ user: null, isLogin: false });
},
};
});
게시물 등록하기 페이지에서 현재 사용자의 닉네임을 가져와야 하는데 API 요청을 하기엔 불필요하다 생각이 들어 Zustand 에서 로그인할 때 저장한 user 의 name 을 가져오려고 했다.
그런데 로그인 후 게시물 등록하기 페이지에 들어오면 내 닉네임이 잘 보이는데 새로고침을 하면 닉네임의 상태값이 사라지게 된다. 🤣 그래서 로컬 스토리지에 상태값을 저장하기로 했다.
persist 란?
이때 간편하게 스토리지에 전역 상태 값을 저장하기 위해 persist 라는 미들웨어를 사용할 수 있다.
persist
Persist 미들웨어를 사용하면 Zustand 상태를 저장소(예: localStorage, AsyncStorage, IndexedDB 등)에 저장하여, 해당 데이터를 유지할 수 있음
[출처: zustand 깃허브 - persist]
persist 사용하여 코드 작성
persist 미들웨어를 사용하여 로컬스토리지에 authStore 라는 키값으로 저장하도록 아래 코드처럼 작성하였다.
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { getCookie, deleteCookie } from '@/utils/cookieUtil';
export type userInfo = {
id: number;
name: string;
};
interface AuthProps {
user: userInfo | null; // 유저 정보
saveUser: (user: userInfo) => void; // 유저 정보 저장
isLogin: boolean; // 로그인 여부
checkLogin: () => void; // 로그인 여부 확인 함수
logout: () => void; // 로그아웃
}
export const useAuthStore = create(
persist<AuthProps>(
(set) => {
return {
user: null,
isLogin: false,
saveUser: (user) => {
set({ user, isLogin: true });
},
checkLogin: () => {
const accessToken = getCookie('accessToken');
if (accessToken) {
set({ isLogin: true });
} else {
set({ user: null, isLogin: false });
}
},
logout: () => {
deleteCookie('accessToken');
set({ user: null, isLogin: false });
},
};
},
{
name: 'authStore',
getStorage: () => {
return localStorage;
},
},
),
);
로컬스토리지 저장
로그인을 하고 위에서 만든 saveUser 를 사용하여 사용자 정보를 로컬 스토리지에 정상적으로 저장이 되었다. 😁
오류 발생
그런데 user 값을 사용하는 페이지에 가니 아래와 같은 오류를 발견하였다.
Text content does not match server-rendered HTML
Hydration Error
위에 에러를 읽어보면 서버에서 렌더링된 HTML과 클라이언트에서 렌더링된 HTML이 일치하지 않다고 한다.
즉, 서버 데이터 !== 클라이언트 데이터
라는 뜻이다.
Zustand 데이터를 클라이언트 측에서 생성하는 상태이므로, 서버에서는 해당 상태를 알 수 없다. 그래서 초기 렌더링을 하면 서버에서는 Zustand 상태값을 없는 상태로 시작을 하는 반면, 클라이언트 측에서는 스토리지에 저장된 값을 사용하다 보니 서버에서 전달받은 상태와 일치하지 않아 Hydraion Error 가 발생한다고 한다. (어렵다...🥲)
오류 해결
구글링을 통해 Hydration Error 를 해결하는 방법을 찾았다.
https://dev.to/abdulsamad/how-to-use-zustands-persist-middleware-in-nextjs-4lb5
How to use Zustand's persist middleware in Next.js
In this article, we'll discuss the common error that arises when using Zustand's persist middleware...
dev.to
useStore 코드 작성
위에 링크를 참고하여 다음과 같이 useStore.ts
코드를 작성했고 useEffect 를 통해 서버 사이드에서 상태를 가져와 로컬에 저장된 상태값으로 업데이트해 주게 된다.
import { useState, useEffect } from 'react';
export const useStore = <T, F>(
store: (callback: (state: T) => unknown) => unknown,
callback: (state: T) => F,
) => {
const result = store(callback) as F;
const [data, setData] = useState<F>();
useEffect(() => {
setData(result);
}, [result]);
return data;
};
useStore 사용하기
사용 방법은 다음과 같다. 위에서 새로 만든 useStore 와 사용할 스토어(useAuthStore) 를 import 한다. 그리고 useStore 의 첫 번째 인자로는 스토어를, 두 번째 인자로는 상태값을 받게 된다.
import { useAuthStore } from '@/store/userAuthStore';
import { useStore } from '@/store/useStore';
...
const Test = () => {
const user = useStore(useAuthStore, (state) => {
return state.user;
});
...
}
그러면 Hydration Error 가 발생하지 않고 로컬 스토어에 저장된 값을 정상적으로 사용할 수 있게 된다.
유의점
새로 만든 커스텀 useStore 를 모든 곳에서 사용하는 것은 아니다. 전역 상태 값을 바꿔주는 것들에는 사용하지 않고 상태 값을 가져오는 경우에만 사용이 된다. 그래서 아래 코드에서는 user, isLogin 값을 사용할 때에만 커스텀 useStore 를 사용해 주고, 나머지 saveUser, checkLogin, logout 에는 원래 사용법대로 사용하면 된다.
export const useAuthStore = create(
persist<AuthProps>(
(set) => {
return {
user: null, // 커스텀 useStore 사용
isLogin: false, // 커스텀 useStore 사용
saveUser: (user) => { // 기존 방식대로
set({ user, isLogin: true });
},
checkLogin: () => { // 기존 방식대로
const accessToken = getCookie('accessToken');
if (accessToken) {
set({ isLogin: true });
} else {
set({ user: null, isLogin: false });
}
},
logout: () => { // 기존 방식대로
deleteCookie('accessToken');
set({ user: null, isLogin: false });
},
};
},
{
name: 'authStore',
getStorage: () => {
return localStorage;
},
},
),
);
이런 식으로 사용하면 된다.
const { checkLogin } = useAuthStore();
const isLogin = useStore(useAuthStore, (state) => {
return state.isLogin;
});
마치며...
Zustand에서 persist 미들웨어를 사용하여 로컬 스토리지에 데이터를 저장하는 법에 대해 이해하게 되었다. 특히 Next.js 는 SSR 방식으로 발생할 수 있는 서버와 클라이언트 간의 데이터 불일치로 인해 Hydration Error 가 발생한다는 것을 알게 되었다.
이전 프로젝트에서도 Zustand 를 사용하였지만 내가 맡은 부분에는 클라이언트 측에서 전역으로 상태를 관리할 부분이 없어 이를 적용해보지 못해 아쉬웠는데 이번에 직접 내가 적용해 보게 되어 만족스러웠다. 😊
![](https://t1.daumcdn.net/keditor/emoticon/friends2/large/047.png)
'💜 리액트 > Next.js' 카테고리의 다른 글
[Next.js] create-next-app 없이 프로젝트 생성하기 (0) | 2024.06.21 |
---|---|
[Next.js] SVG 컴포넌트 사용하는 방법 (React 보다 짱편함!) (0) | 2024.06.04 |
[Next.js] React 대신 Next.js 사용하는 이유?!? (1) | 2024.06.03 |
FE 개발자가 되고 싶은 짱잼이
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!