diff --git a/frontend/.gitignore b/frontend/.gitignore index f0abd802..348bb0b4 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -11,6 +11,7 @@ node_modules dist dist-ssr *.local +.env* # Editor directories and files .vscode/* diff --git a/frontend/apps/client/src/routes/oauth.redirect/calendar/index.tsx b/frontend/apps/client/src/routes/oauth.redirect/calendar/index.tsx index cd68906c..8d3cb75c 100644 --- a/frontend/apps/client/src/routes/oauth.redirect/calendar/index.tsx +++ b/frontend/apps/client/src/routes/oauth.redirect/calendar/index.tsx @@ -1,6 +1,7 @@ +import { useEffectOnce } from '@endolphin/core/hooks'; import { useQueryClient } from '@tanstack/react-query'; import { createFileRoute, useNavigate } from '@tanstack/react-router'; -import { useEffect } from 'react'; +import { useCallback } from 'react'; import { calendarKeys } from '@/features/my-calendar/api/keys'; @@ -8,12 +9,14 @@ const Redirect = () => { const queryClient = useQueryClient(); const navigate = useNavigate(); - useEffect(() => { + const clearCalendarCache = useCallback(() => { (async () => { await queryClient.invalidateQueries({ queryKey: calendarKeys.all }); navigate({ to: '/my-calendar' }); })(); }, [queryClient, navigate]); + + useEffectOnce({ callback: clearCalendarCache }); return null; }; diff --git a/frontend/apps/client/src/routes/oauth.redirect/login/index.tsx b/frontend/apps/client/src/routes/oauth.redirect/login/index.tsx index 8e3a91b9..70eccb09 100644 --- a/frontend/apps/client/src/routes/oauth.redirect/login/index.tsx +++ b/frontend/apps/client/src/routes/oauth.redirect/login/index.tsx @@ -1,5 +1,6 @@ +import { useEffectOnce } from '@endolphin/core/hooks'; import { createFileRoute, useSearch } from '@tanstack/react-router'; -import { useEffect } from 'react'; +import { useCallback } from 'react'; import { useJWTMutation } from '@/features/login/api/mutations'; import { getLastRoutePath } from '@/utils/route'; @@ -10,11 +11,14 @@ const Redirect = () => { const params: { code: string } = useSearch({ from: '/oauth/redirect/login/' }); const { code } = params; - useEffect(() => { - if (code) { - loginMutate({ code, lastPath }); - } - }, [code, loginMutate, lastPath]); + const loginCache = useCallback(() => { + loginMutate({ code, lastPath }); + }, [loginMutate, code, lastPath]); + + useEffectOnce({ + condition: Boolean(code), + callback: loginCache, + }); return null; }; diff --git a/frontend/packages/core/src/hooks/index.ts b/frontend/packages/core/src/hooks/index.ts index f9880f43..718c9aaa 100644 --- a/frontend/packages/core/src/hooks/index.ts +++ b/frontend/packages/core/src/hooks/index.ts @@ -1,3 +1,4 @@ export * from './useClickOutside'; +export * from './useEffectOnce'; export * from './useSafeContext'; export * from './useSelectTime'; \ No newline at end of file diff --git a/frontend/packages/core/src/hooks/useEffectOnce.ts b/frontend/packages/core/src/hooks/useEffectOnce.ts new file mode 100644 index 00000000..8a0f28c9 --- /dev/null +++ b/frontend/packages/core/src/hooks/useEffectOnce.ts @@ -0,0 +1,28 @@ +import { useEffect, useRef } from 'react'; + +interface UseEffectOnceOptions { + condition?: boolean; + callback: () => void; +} + +/** + * + * @param options + * @param options.condition - 콜백을 실행할 조건 (기본값: true) + * @param options.callback - 한 번만 실행할 콜백 함수. useCallback을 사용하여 메모이제이션하는 것이 좋습니다. + */ +export const useEffectOnce = ({ condition = true, callback }: UseEffectOnceOptions) => { + const isCalled = useRef(false); + + useEffect(() => { + if (condition && !isCalled.current) { + callback(); + } + + return () => { + if (!isCalled.current) { + isCalled.current = true; + } + }; + }, [condition, callback]); +}; \ No newline at end of file