๐จ TroubleShotting
๋ฌธ์ ๋ฐ์์ ๋ฐฐ๊ฒฝ - As Is
Next.js์ App-Router๋ฅผ ์ฌ์ฉํ๋ฉฐ ์ข์์๋ฅผ ๊ตฌํํ๋ ์ค, ๋๊ด์ ์
๋ฐ์ดํธ๋ฅผ ๊ตฌํํ๊ณ ์ถ์๋ค.
๊ทธ๋์ ์ฒ์์๋ ์ฝ๋๋ฅผ ์ด๋ ๊ฒ ์งฐ๋ค.
submitQuizLike()๋ผ๋ ๋น๋๊ธฐ ํต์ ๋ก์ง์ด ์คํ๋๊ธฐ ์ ์ setIsLiked๋ก ๋๊ด์ ์
๋ฐ์ดํธ๋ฅผ ํด์ผ์ง๋ผ๋ ํ์ ํ๊ณ ์ผ๋ฌด์ง ์๋ง(?)๊ณผ ํจ๊ป..ใ
ใ
ใ
const LikeQuiz = ({ quiz_id }: { quiz_id: string }) => {
...
const [isLiked, setIsLiked] = useState(userData && quizLikeData && quizLikeData.users?.includes(userData.user_id));
const queryClient = useQueryClient();
const handleSubmitLike = async () => {
setIsLiked((prev) => !prev); ---------> submitQuizLike ์ ์ ๋๊ด์ ์
๋ฐ์ดํธ ํด์ผ์ง~
await submitQuizLike(quiz_id, userData?.user_id ?? '');
queryClient.invalidateQueries({ queryKey: [QUIZLIKE_QUERY_KEY, quiz_id] });
};
console.log('isLiked ====>>', isLiked);
console.log('-----------------------------------');
return (
<>
<form action={handleSubmitLike}>
<button type="submit" name="like">
{isLiked ? <FaHeart className="text-red-500" /> : <FaRegHeart />}
</button>
</form>
</>
);
};
๊ทธ๋ฌ๋๋ ๋น์ฐํ(?) ๋๊ด์ ์ ๋ฐ์ดํธ๊ฐ ์ ๋๋ค.
๋๊ฐ๋ด๋ ๋น๋๊ธฐ ํต์ ์ด ์๋ฃ๋ ๋๊น์ง ๊ธฐ๋ค๋ ธ๋ค๊ฐ setIsLiked๊ฐ ์คํ๋๊ณ ์๋ค.
์๋ํ๋ฉด ์ํ ์
๋ฐ์ดํธ์ ์ํ re-๋ ๋๋ง์ ์ํ ์
๋ฐ์ดํธ๋ฅผ ๊ฐ์ธ๋ ํจ์ ์ ์ฒด(์ฌ๊ธฐ์๋ handleSubmitLike ํจ์)๊ฐ ์ข
๋ฃ๋ ๋ค ์คํ๋๊ธฐ ๋๋ฌธ์ด๋ค.
์๋ ์๊ณ ๋ณด๋ฉด ๋๋ฌด ๋น์ฐํ ์ด์ผ๊ธด๋ฐ ์ ๋๋ ๋งจ๋ ๊น๋จน๋๊ฐ?
ํ๊ธด ์ด๋ ๊ฒ ์ฝ๊ฒ ๋ ๊ฒ์ด์๋ค๋ฉด optimistic-update๋ฅผ ์ํ ํ
๋ค์ด ์์๊ฒ ์ง.
๊ด๊ฑด์ ์ํ์
๋ฐ์ดํธ์ ๋น๋๊ธฐ ํต์ ๋ก์ง์ด ํ๋์ ํจ์ ์์ ์๋๋ผ๋ ๊ตฌ๋ถ๋์ด ์คํ๋๋๋ก ํ๋ ๊ฒ์ด์๋ค.
How(๊ณผ์ ) ?
๋ ๊ฐ์ง ๋ฐฉ๋ฒ์ด ์๊ฐ๋ฌ๋ค.
1. ์ ๋ฒ์ ํ๋ฒ ํฌ์คํ ํ๋ Next.js ๊ณต์๋ฌธ์์์ ์ ์ํ๋ useOptimistic() ์ด๋ผ๋ canary ๋ฒ์ ์ react-hook
2. ๋๋์ useTransition()์ผ๋ก๋ ๊ฐ๋ฅํ ๊ฒ ๊ฐ์๋ค.
์ํ์ฐฉ์ค 1
useOptimistic()
๋จผ์ useOptimistic() ๋ถํฐ ์๋ํ๋ ์ค
๋ถ๋ช
์ข์์ ์ทจ์๋ฅผ ๋๋ ๋๋ฐ, ๋ค์ ์ข์์๊ฐ ๋ ์ํ๋ก ๋์๊ฐ๋ ํ์์ด ๋ฐ์ํ๋ค.
์ฝ๋๋ ์ด๋ฌํ๋ค.
const LikeQuiz = ({ quiz_id }: { quiz_id: string }) => {
...
const [isLiked, setIsLiked] = useState(userData && quizLikeData && quizLikeData.users?.includes(userData.user_id));
const queryClient = useQueryClient();
// useOptimistic
const [optimisticLike, updateOptimisticLike] = useOptimistic(isLiked, (state) => !state);
const handleSubmitLike = async () => {
updateOptimisticLike(isLiked); -------------> Optimistic Update
await submitQuizLike(quiz_id, userData?.user_id ?? '');
queryClient.invalidateQueries({ queryKey: [QUIZLIKE_QUERY_KEY, quiz_id] });
};
console.log('isLiked ====>>', isLiked);
console.log('optimisticLike =>', optimisticLike);
console.log('-----------------------------------');
return (
<>
<form action={handleSubmitLike}>
<button type="submit" name="like">
{optimisticLike ? <FaHeart className="text-red-500" /> : <FaRegHeart />}
</button>
</form>
</>
);
};
optimisticLike์ useState๋ก ์ ์ํ isLiked ์ฌ์ด์ ๋ณ๋์ ์ฒ๋ฆฌ๋ฅผ ํด์ฃผ์ง ์์๋,
์ฆ ๋ ๊ฐ์ ์ํ๊ฐ ๋ณ๊ฐ๊ฐ ์๋๊ณ
๊ฒฐ๊ตญ optimisticLike์ ์ํ๋ isLiked์ ์ํ๋ฅผ ๋ฐ๋ผ๊ฐ๋?
ํ๋ ์๋ฌธ์ด ๋ค์๋ค.
updateOptimistic์ผ๋ก ๋๊ด์ ์
๋ฐ์ดํธ๋ฅผ ํด๋ ๊ฒฐ๊ตญ ์๋ isLiked ์ํ๋ก ๋์๊ฐ๋ ๊ฒ ๊ฐ์๊ธฐ ๋๋ฌธ์ด๋ค.
๊ทธ๋ฌ๋ฉด handleSubmitLike ํจ์์ optimisticLike๋ง ์
๋ฐ์ดํธํ ๊ฒ์ด ์๋๋ผ setIsLiked() ๋ก์ง๋ ์ถ๊ฐํด์ผ๊ฒ ๊ตฌ๋!
์ถ๊ฐ๋ก, ๋น๋๊ธฐ ํต์ ์ด ์ฑ๊ณตํ ์๋ ์๊ณ ์คํจํ ์๋ ์์ผ๋ useEffect ๋ด์์ queryClient.invalidateQueires๋ฅผ ํ ๊ฒฐ๊ณผ๋ก setIsLiked๋ฅผ ํ์คํ ์ฌํ์ธ ํด์ผ๊ฒ ๋ค ์ถ์๋ค.
const LikeQuiz = ({ quiz_id }: { quiz_id: string }) => {
...
const queryClient = useQueryClient();
const [isLiked, setIsLiked] = useState(userData && quizLikeData && quizLikeData.users?.includes(userData.user_id));
const [optimisticLike, updateOptimisticLike] = useOptimistic(isLiked, (state) => !state);
useEffect(() => {
// (3) ๋น๋๊ธฐ ํต์ ์ฑ๊ณต or ์คํจ ์ฌ๋ถ์ ๋ฐ๋ฅธ ํ์คํ isLiked ์ฌ์ค์
setIsLiked(userData && quizLikeData && quizLikeData.users?.includes(userData.user_id));
}, [quizLikeData, userData]);
const handleSubmitLike = async () => {
updateOptimisticLike(isLiked); // ---------> (1) ๋๊ด์ ์
๋ฐ์ดํธ
setIsLiked((prev) => !prev); // ---------> (2) + ๊ฒฐ์ ์ ์ธ setIsLiked
await submitQuizLike(quiz_id, userData?.user_id ?? '');
queryClient.invalidateQueries({ queryKey: [QUIZLIKE_QUERY_KEY, quiz_id] });
};
console.log('isLiked ====>>', isLiked);
console.log('optimisticLike =>', optimisticLike);
console.log('-----------------------------------');
return (
<>
<form action={handleSubmitLike}>
<button type="submit" name="like">
{optimisticLike ? <FaHeart className="text-red-500" /> : <FaRegHeart />}
</button>
</form>
</>
);
};
์ฑ๊ณต!!
์ข์์ ๋ฒํผ์ ๋๋ฅด์๋ง์ ๋๊ด์ ์
๋ฐ์ดํธ๋ ์ ๋๊ณ ,
-> setIsLiked๋ ๋น๋๊ธฐ ํต์ ํ์ ๋๋ฉฐ,
-> ๋ง์ง๋ง์ useEffect๋ก ๋ค์ ํ๋ฒ ๋น๋๊ธฐ ํต์ ์ ์ฑ๊ณต/์คํจ ์ฌ๋ถ์ ๋ฐ๋ผ ํ์คํ setIsLiked๊ฐ ๋๋,
-> ์ด์ ๊ณผ ์ํ๊ฐ ๊ฐ๋ค๋ฉด UI๊ฐ ๋ณํ์ง ์์์ ๋งค๋๋ฝ๊ฒ ๋ณด์ด๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
ํ์ง๋ง ์ด useOptimistic()์ด๋ผ๋ react-hook์ ์ด๋๊น์ง๋ ์์ง ๋ฆฌ์กํธ์ canary-version์ด๋ผ๋ ์ ์ด ์ข ์ฐ์ฐํ๋ค.
์ฌ์ค ๋ด ๋จธ๋ฆฟ์์๋ useTransition() hook์ด ๋จผ์ ๋ ์ฌ๋๋ค. ๊ทธ๋์
์ํ์ฐฉ์ค 2
useTranstion()
https://react.dev/reference/react/useTransition
useTransition์ ์ฌ์ฉํด์ ์ข์์ ๋ฒํผ์ ๋๋ฅด๋ฉด ๋น๋๊ธฐ ํต์ ์ด ์์๋๊ณ ,
์๋ฃ๋ ๋๊น์ง isPending ์ด๋ผ๋ ์ํ๊ฐ true๊ฐ ๋๋ค.
๊ฑฐ๊พธ๋ก ๋งํ๋ฉด isPending์ด true๋ผ๋ ๊ฒ์ ์ข์์ ๋ฒํผ์ ๋๋ ๋ค๋ ๋ง์ด ๋๋ค.
๊ทธ๋์ isPending์ด true๋ฉด ์ผ๋จ isLiked ์ํ๋ฅผ ์ด์ ๊ฒ๊ณผ ๋ฐ๋๋๋๋ก ๋ฐ๊พธ๋ฉด ์ด๋จ๊น?
๋ผ๋ ์๊ฐ์ด ๋ค์๋ค.
๊ทธ ๋ค์ ์์์์ฒ๋ผ ๋น๋๊ธฐ ํต์ ์ ์ฑ๊ณต/์คํจ ์ฌ๋ถ์ ๋ฐ๋ผ์ ํ์คํ ์ฌ์ค์ ํด์ฃผ๋ฉด ๋์๋?
what(๊ฒฐ๊ณผ) - To Be
๊ฒฐ๊ณผ๋ ์ฑ๊ณต!!
์ฝ๋๋ ๋ค์๊ณผ ๊ฐ๋ค.
const LikeQuiz = ({ quiz_id }: { quiz_id: string }) => {
...
const [isLiked, setIsLiked] = useState(userData && quizLikeData && quizLikeData.users?.includes(userData.user_id));
const queryClient = useQueryClient();
// useTransition
const [isPending, startTransition] = useTransition();
useEffect(() => {
// (3) ๋น๋๊ธฐ ํต์ ์ ์ฑ๊ณต/์คํจ ์ฌ๋ถ์ ๋ฐ๋ฅธ ํ์คํ isLiked ์ฌ์ค์
setIsLiked(userData && quizLikeData && quizLikeData.users?.includes(userData.user_id));
}, [quizLikeData, userData]);
useEffect(() => {
// (2) isPending์ด true๋ฉด ์ผ๋จ isLiked ์ํ๋ฅผ ์ด์ ๊ณผ ๋ฐ๋๋ก ์
๋ฐ์ดํธ
isPending && setIsLiked((prev) => !prev);
}, [isPending]);
const handleSubmitLike = async () => {
startTransition(async () => { // ---------> (1) ๋น๋๊ธฐ ํต์ ์์!
await submitQuizLike(quiz_id, userData?.user_id ?? '');
queryClient.invalidateQueries({ queryKey: [QUIZLIKE_QUERY_KEY, quiz_id] });
});
};
console.log('isLiked ====>>', isLiked);
console.log('-----------------------------------');
return (
<>
<form action={handleSubmitLike}>
<button type="submit" name="like">
{isLiked ? <FaHeart className="text-red-500" /> : <FaRegHeart />}
</button>
</form>
</>
);
};
๋ฒํผ์ ๋๋ฅด๋ฉด isPending์ด true๊ฐ ๋๊ณ
-> isLiked ์ํ๋ ๋ฌด์กฐ๊ฑด ์ด์ ๊ณผ ๋ฐ๋ ์ํ๋ก ๋ฐ๋๊ณ
-> ๋น๋๊ธฐ ํต์ ์ด ์๋ฃ๋ ํ, ๋น๋๊ธฐ ํต์ ์ ์ฑ๊ณต/์คํจ ์ฌ๋ถ์ ๋ฐ๋ผ isLiked ์ํ๊ฐ ์ข ๋ ํ์คํ ๋ฐ๋๋ค.
๊ฐ์ธ์ ์ผ๋ก๋ ์ด ๋ฐฉ๋ฒ์ด ๋ก์ง๋ ๋ ๊น๋ํด์ ๋ง์์ ๋ ๋ค.
๐ก ์๋กญ๊ฒ ์๊ฒ๋ ์
์๋กญ๊ฒ ์๊ฒ๋ ๊ฒ์ ์๋์ง๋ง
์์ง๋ง์! ํด๋น ํจ์๊ฐ ์ข
๋ฃ๋์ด์ผ re-๋ ๋๋ง์ด ๋๋ค!!
๋ง์ฝ Server-Action์ ์ฌ์ฉํ์ง ์๋๋ค๋ฉด react-Query์ onMutate, onError, onSettled์
setQueryData & getQueryData๋ก ๋๊ด์ ์
๋ฐ์ดํธ๋ฅผ ๊ตฌํํ์ํ
๋ฐ
๋ญ๊ฐ ์ ํด์ง ๋ฐฉ๋ฒ ๋ง๊ณ ์ค์ค๋ก ์๊ฐํด๋ดค๋ค๋ ์ ์ด ์๋ก์ ๋ค.
'TIL' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
npx? npm? pnpm? (2) | 2024.11.10 |
---|---|
๋ชจ๋ฌ์ฐฝ ๊ตฌํํ๊ธฐ - RootLayout์ ์์น vs createPortal (0) | 2024.07.10 |
๋ฌดํ์คํฌ๋กค intersection-observer ์์ด ๊ตฌํํด๋ณด๊ธฐ (0) | 2024.07.01 |
2024.06.20 SQL๋ฌธ์ผ๋ก ๊ธฐ์กด ๋ฐฐ์ด์ ๋ฐ์ดํฐ ๊ฐ์๋ผ์ฐ์ง ์๊ณ ์ถ๊ฐํ๊ธฐ (0) | 2024.06.22 |
2024.06.10 drag_๋ผ์ด๋ธ๋ฌ๋ฆฌ_์์ด_๊ตฌํ_์ค_ํธ๋ฌ๋ธ์ํ 1 (1) | 2024.06.12 |