@sonhoseong/mfa-lib 1.3.7 → 1.3.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/button/ScrollTopButton.js +5 -3
- package/dist/components/error/ErrorBoundary.js +14 -4
- package/dist/components/error/NotFound.d.ts +20 -0
- package/dist/components/error/NotFound.d.ts.map +1 -0
- package/dist/components/error/NotFound.js +84 -0
- package/dist/components/error/index.d.ts +2 -0
- package/dist/components/error/index.d.ts.map +1 -1
- package/dist/components/error/index.js +1 -0
- package/dist/components/icons/Icons.d.ts +51 -0
- package/dist/components/icons/Icons.d.ts.map +1 -0
- package/dist/components/icons/Icons.js +100 -0
- package/dist/components/icons/index.d.ts +5 -0
- package/dist/components/icons/index.d.ts.map +1 -0
- package/dist/components/icons/index.js +4 -0
- package/dist/components/index.d.ts +3 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +6 -0
- package/dist/components/layout/Container.js +7 -2
- package/dist/components/loading/DeferredComponent.d.ts +19 -0
- package/dist/components/loading/DeferredComponent.d.ts.map +1 -0
- package/dist/components/loading/DeferredComponent.js +32 -0
- package/dist/components/loading/GlobalLoading.js +14 -3
- package/dist/components/loading/index.d.ts +1 -0
- package/dist/components/loading/index.d.ts.map +1 -1
- package/dist/components/loading/index.js +1 -0
- package/dist/components/logo/Logo.d.ts +2 -0
- package/dist/components/logo/Logo.d.ts.map +1 -1
- package/dist/components/logo/Logo.js +13 -4
- package/dist/components/modal/ModalContainer.js +17 -8
- package/dist/components/modal/ModalContext.js +2 -3
- package/dist/components/navigation/AppNavbar.js +21 -9
- package/dist/components/navigation/AppSidebar.css +58 -3
- package/dist/components/navigation/AppSidebar.d.ts +1 -1
- package/dist/components/navigation/AppSidebar.d.ts.map +1 -1
- package/dist/components/navigation/AppSidebar.js +58 -15
- package/dist/components/navigation/Footer.d.ts +15 -0
- package/dist/components/navigation/Footer.d.ts.map +1 -0
- package/dist/components/navigation/Footer.js +12 -0
- package/dist/components/navigation/Header.d.ts.map +1 -1
- package/dist/components/navigation/Header.js +17 -4
- package/dist/components/navigation/Lnb.d.ts +2 -7
- package/dist/components/navigation/Lnb.d.ts.map +1 -1
- package/dist/components/navigation/Lnb.js +34 -6
- package/dist/components/navigation/StickyNav.js +19 -11
- package/dist/components/navigation/index.d.ts +1 -0
- package/dist/components/navigation/index.d.ts.map +1 -1
- package/dist/components/navigation/index.js +1 -0
- package/dist/components/page/LoginPage.d.ts +4 -1
- package/dist/components/page/LoginPage.d.ts.map +1 -1
- package/dist/components/page/LoginPage.js +146 -21
- package/dist/components/remote/RemoteErrorBoundary.d.ts +28 -0
- package/dist/components/remote/RemoteErrorBoundary.d.ts.map +1 -0
- package/dist/components/remote/RemoteErrorBoundary.js +44 -0
- package/dist/components/remote/RemoteErrorFallback.d.ts +16 -0
- package/dist/components/remote/RemoteErrorFallback.d.ts.map +1 -0
- package/dist/components/remote/RemoteErrorFallback.js +76 -0
- package/dist/components/remote/index.d.ts +8 -0
- package/dist/components/remote/index.d.ts.map +1 -0
- package/dist/components/remote/index.js +5 -0
- package/dist/components/router/BrowserRouter.d.ts +13 -0
- package/dist/components/router/BrowserRouter.d.ts.map +1 -0
- package/dist/components/router/BrowserRouter.js +17 -0
- package/dist/components/router/RouteGuard.d.ts +79 -0
- package/dist/components/router/RouteGuard.d.ts.map +1 -0
- package/dist/components/router/RouteGuard.js +86 -0
- package/dist/components/router/index.d.ts +4 -0
- package/dist/components/router/index.d.ts.map +1 -0
- package/dist/components/router/index.js +2 -0
- package/dist/components/toast/ToastContainer.js +17 -6
- package/dist/components/toast/ToastContext.js +2 -3
- package/dist/hooks/index.d.ts +9 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +15 -1
- package/dist/hooks/use-auth.d.ts +2 -1
- package/dist/hooks/use-auth.d.ts.map +1 -1
- package/dist/hooks/use-auth.js +19 -18
- package/dist/hooks/use-debounce.d.ts +56 -0
- package/dist/hooks/use-debounce.d.ts.map +1 -0
- package/dist/hooks/use-debounce.js +140 -0
- package/dist/hooks/use-effect-once.d.ts +77 -0
- package/dist/hooks/use-effect-once.d.ts.map +1 -0
- package/dist/hooks/use-effect-once.js +124 -0
- package/dist/hooks/use-error-notification.d.ts +1 -1
- package/dist/hooks/use-error-notification.js +1 -1
- package/dist/hooks/use-global-loading.d.ts +1 -1
- package/dist/hooks/use-global-loading.js +1 -1
- package/dist/hooks/use-initialize.d.ts +8 -1
- package/dist/hooks/use-initialize.d.ts.map +1 -1
- package/dist/hooks/use-initialize.js +126 -23
- package/dist/hooks/use-modal.d.ts +21 -5
- package/dist/hooks/use-modal.d.ts.map +1 -1
- package/dist/hooks/use-modal.js +57 -17
- package/dist/hooks/use-navigate.d.ts +1 -1
- package/dist/hooks/use-navigate.js +1 -1
- package/dist/hooks/use-network-status.d.ts +15 -0
- package/dist/hooks/use-network-status.d.ts.map +1 -0
- package/dist/hooks/use-network-status.js +49 -0
- package/dist/hooks/use-permission.d.ts +22 -0
- package/dist/hooks/use-permission.d.ts.map +1 -0
- package/dist/hooks/use-permission.js +73 -0
- package/dist/hooks/use-recent-menu.d.ts +46 -0
- package/dist/hooks/use-recent-menu.d.ts.map +1 -0
- package/dist/hooks/use-recent-menu.js +169 -0
- package/dist/hooks/use-scroll-restoration.d.ts +51 -0
- package/dist/hooks/use-scroll-restoration.d.ts.map +1 -0
- package/dist/hooks/use-scroll-restoration.js +143 -0
- package/dist/hooks/use-supabase-auth.d.ts +49 -0
- package/dist/hooks/use-supabase-auth.d.ts.map +1 -0
- package/dist/hooks/use-supabase-auth.js +229 -0
- package/dist/hooks/use-track-history.d.ts +2 -1
- package/dist/hooks/use-track-history.d.ts.map +1 -1
- package/dist/hooks/use-track-history.js +14 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/network/axios-factory.d.ts +30 -1
- package/dist/network/axios-factory.d.ts.map +1 -1
- package/dist/network/axios-factory.js +192 -24
- package/dist/network/index.d.ts +3 -1
- package/dist/network/index.d.ts.map +1 -1
- package/dist/network/index.js +5 -1
- package/dist/network/supabase-client.d.ts +28 -0
- package/dist/network/supabase-client.d.ts.map +1 -0
- package/dist/network/supabase-client.js +46 -0
- package/dist/store/app-store.d.ts +222 -12
- package/dist/store/app-store.d.ts.map +1 -1
- package/dist/store/app-store.js +46 -29
- package/dist/store/index.d.ts +2 -0
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +3 -0
- package/dist/store/menu-slice.d.ts +96 -0
- package/dist/store/menu-slice.d.ts.map +1 -0
- package/dist/store/menu-slice.js +98 -0
- package/dist/store/recent-menu-slice.d.ts +209 -0
- package/dist/store/recent-menu-slice.d.ts.map +1 -0
- package/dist/store/recent-menu-slice.js +110 -0
- package/dist/store/store-access.d.ts +1 -1
- package/dist/store/store-access.js +1 -1
- package/dist/types/index.d.ts +74 -17
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/service.d.ts +1 -1
- package/dist/types/service.js +1 -1
- package/dist/utils/classnames.d.ts +65 -0
- package/dist/utils/classnames.d.ts.map +1 -0
- package/dist/utils/classnames.js +98 -0
- package/dist/utils/formatter.d.ts +78 -0
- package/dist/utils/formatter.d.ts.map +1 -0
- package/dist/utils/formatter.js +216 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +5 -0
- package/dist/utils/permission.d.ts +33 -0
- package/dist/utils/permission.d.ts.map +1 -0
- package/dist/utils/permission.js +132 -0
- package/dist/utils/query-string.d.ts +67 -0
- package/dist/utils/query-string.d.ts.map +1 -0
- package/dist/utils/query-string.js +136 -0
- package/dist/utils/storage.d.ts +1 -1
- package/dist/utils/storage.js +1 -1
- package/dist/utils/validation.d.ts +98 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +260 -0
- package/package.json +5 -3
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Toast Container - KOMCA 패턴
|
|
3
|
+
* 토스트 알림 UI 렌더링
|
|
4
|
+
*/
|
|
5
|
+
import React from 'react';
|
|
2
6
|
import { useToast } from './ToastContext';
|
|
3
7
|
// 타입별 아이콘
|
|
4
8
|
const icons = {
|
|
@@ -36,10 +40,17 @@ const ToastContainer = ({ position = 'top-right' }) => {
|
|
|
36
40
|
};
|
|
37
41
|
if (toasts.length === 0)
|
|
38
42
|
return null;
|
|
39
|
-
return (
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
return (React.createElement("div", { style: getPositionStyles() },
|
|
44
|
+
React.createElement("div", { className: "toast-list" }, toasts.map((toast) => (React.createElement("div", { key: toast.id, className: `toast-item toast-${toast.type}`, style: {
|
|
45
|
+
backgroundColor: colors[toast.type].bg,
|
|
46
|
+
borderLeftColor: colors[toast.type].border,
|
|
47
|
+
} },
|
|
48
|
+
React.createElement("div", { className: "toast-icon", style: { color: colors[toast.type].icon } }, icons[toast.type]),
|
|
49
|
+
React.createElement("div", { className: "toast-content" },
|
|
50
|
+
toast.title && React.createElement("div", { className: "toast-title" }, toast.title),
|
|
51
|
+
React.createElement("div", { className: "toast-message" }, toast.message)),
|
|
52
|
+
React.createElement("button", { className: "toast-close", onClick: () => removeToast(toast.id), "aria-label": "\uB2EB\uAE30" }, "\u2715"))))),
|
|
53
|
+
React.createElement("style", null, `
|
|
43
54
|
.toast-list {
|
|
44
55
|
display: flex;
|
|
45
56
|
flex-direction: column;
|
|
@@ -120,6 +131,6 @@ const ToastContainer = ({ position = 'top-right' }) => {
|
|
|
120
131
|
background: rgba(0, 0, 0, 0.1);
|
|
121
132
|
color: #374151;
|
|
122
133
|
}
|
|
123
|
-
`
|
|
134
|
+
`)));
|
|
124
135
|
};
|
|
125
136
|
export default ToastContainer;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
1
|
/**
|
|
3
2
|
* Toast Context - KOMCA 패턴
|
|
4
3
|
* 전역 토스트 알림 상태 관리
|
|
5
4
|
*/
|
|
6
|
-
import { createContext, useContext, useCallback, useState } from 'react';
|
|
5
|
+
import React, { createContext, useContext, useCallback, useState } from 'react';
|
|
7
6
|
const ToastContext = createContext(null);
|
|
8
7
|
/**
|
|
9
8
|
* Toast Provider
|
|
@@ -41,7 +40,7 @@ export const ToastProvider = ({ children, defaultDuration = 3000, maxToasts = 5,
|
|
|
41
40
|
const info = useCallback((message, title) => {
|
|
42
41
|
addToast({ type: 'info', message, title });
|
|
43
42
|
}, [addToast]);
|
|
44
|
-
return (
|
|
43
|
+
return (React.createElement(ToastContext.Provider, { value: { toasts, addToast, removeToast, success, error, warning, info } }, children));
|
|
45
44
|
};
|
|
46
45
|
/**
|
|
47
46
|
* useToast Hook
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Hooks 모듈
|
|
2
|
+
* Hooks 모듈
|
|
3
3
|
*/
|
|
4
4
|
export * from './use-auth';
|
|
5
|
+
export * from './use-supabase-auth';
|
|
5
6
|
export * from './use-initialize';
|
|
6
7
|
export * from './use-track-history';
|
|
7
8
|
export * from './use-navigate';
|
|
8
9
|
export * from './use-global-loading';
|
|
9
10
|
export * from './use-modal';
|
|
10
11
|
export * from './use-error-notification';
|
|
12
|
+
export { default as useNetworkStatus } from './use-network-status';
|
|
13
|
+
export type { ConnectionType } from './use-network-status';
|
|
14
|
+
export * from './use-permission';
|
|
15
|
+
export * from './use-recent-menu';
|
|
16
|
+
export * from './use-debounce';
|
|
17
|
+
export * from './use-scroll-restoration';
|
|
18
|
+
export * from './use-effect-once';
|
|
11
19
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,cAAc,YAAY,CAAC;AAG3B,cAAc,kBAAkB,CAAC;AAGjC,cAAc,qBAAqB,CAAC;AAGpC,cAAc,gBAAgB,CAAC;AAG/B,cAAc,sBAAsB,CAAC;AAGrC,cAAc,aAAa,CAAC;AAG5B,cAAc,0BAA0B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,cAAc,YAAY,CAAC;AAG3B,cAAc,qBAAqB,CAAC;AAGpC,cAAc,kBAAkB,CAAC;AAGjC,cAAc,qBAAqB,CAAC;AAGpC,cAAc,gBAAgB,CAAC;AAG/B,cAAc,sBAAsB,CAAC;AAGrC,cAAc,aAAa,CAAC;AAG5B,cAAc,0BAA0B,CAAC;AAGzC,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACnE,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAG3D,cAAc,kBAAkB,CAAC;AAGjC,cAAc,mBAAmB,CAAC;AAGlC,cAAc,gBAAgB,CAAC;AAG/B,cAAc,0BAA0B,CAAC;AAGzC,cAAc,mBAAmB,CAAC"}
|
package/dist/hooks/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Hooks 모듈
|
|
2
|
+
* Hooks 모듈
|
|
3
3
|
*/
|
|
4
4
|
// Auth Hooks
|
|
5
5
|
export * from './use-auth';
|
|
6
|
+
// Supabase Auth Hooks
|
|
7
|
+
export * from './use-supabase-auth';
|
|
6
8
|
// Initialize Hook
|
|
7
9
|
export * from './use-initialize';
|
|
8
10
|
// Track History Hook
|
|
@@ -15,3 +17,15 @@ export * from './use-global-loading';
|
|
|
15
17
|
export * from './use-modal';
|
|
16
18
|
// Error Notification Hook
|
|
17
19
|
export * from './use-error-notification';
|
|
20
|
+
// Network Status Hook
|
|
21
|
+
export { default as useNetworkStatus } from './use-network-status';
|
|
22
|
+
// Permission Hook
|
|
23
|
+
export * from './use-permission';
|
|
24
|
+
// Recent Menu Hook
|
|
25
|
+
export * from './use-recent-menu';
|
|
26
|
+
// Debounce & Throttle Hooks
|
|
27
|
+
export * from './use-debounce';
|
|
28
|
+
// Scroll Restoration Hook
|
|
29
|
+
export * from './use-scroll-restoration';
|
|
30
|
+
// Effect Once & Lifecycle Hooks
|
|
31
|
+
export * from './use-effect-once';
|
package/dist/hooks/use-auth.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Auth Hooks
|
|
2
|
+
* Auth Hooks
|
|
3
3
|
* 로그인, 로그아웃, 토큰 갱신
|
|
4
4
|
*/
|
|
5
5
|
import { User } from '../types';
|
|
@@ -29,6 +29,7 @@ export declare function useLogout(logoutApi?: LogoutFn): () => Promise<void>;
|
|
|
29
29
|
export declare function useTokenRefresh(refreshApi?: RefreshFn): () => Promise<string | null>;
|
|
30
30
|
/**
|
|
31
31
|
* 인증 상태 확인 Hook
|
|
32
|
+
* useSelector를 사용하여 상태 변경시 리렌더링 보장
|
|
32
33
|
*/
|
|
33
34
|
export declare function useAuthState(): {
|
|
34
35
|
isAuthenticated: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-auth.d.ts","sourceRoot":"","sources":["../../src/hooks/use-auth.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"use-auth.d.ts","sourceRoot":"","sources":["../../src/hooks/use-auth.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAGhC,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,IAAI,CAAC;CACZ;AAGD,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAGD,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,EAAE,YAAY,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC;AAGxE,MAAM,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;AAG3C,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;AAErD;;GAEG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,aACN,YAAY,KAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAuChF;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,SAAS,CAAC,EAAE,QAAQ,SACf,OAAO,CAAC,IAAI,CAAC,CAuB3C;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,UAAU,CAAC,EAAE,SAAS,SACvB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA2BpD;AAED;;;GAGG;AACH,wBAAgB,YAAY;;;;EAS3B"}
|
package/dist/hooks/use-auth.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Auth Hooks
|
|
2
|
+
* Auth Hooks
|
|
3
3
|
* 로그인, 로그아웃, 토큰 갱신
|
|
4
4
|
*/
|
|
5
5
|
import { useCallback } from 'react';
|
|
6
|
-
import {
|
|
6
|
+
import { useSelector } from 'react-redux';
|
|
7
|
+
import { getStore, setAccessToken, setUser, logout, selectAccessToken, selectUser } from '../store/app-store';
|
|
7
8
|
import { storage } from '../utils/storage';
|
|
8
9
|
/**
|
|
9
10
|
* 로그인 Hook
|
|
@@ -11,9 +12,9 @@ import { storage } from '../utils/storage';
|
|
|
11
12
|
export function useLogin(loginApi) {
|
|
12
13
|
return useCallback(async (request) => {
|
|
13
14
|
try {
|
|
15
|
+
const store = getStore();
|
|
14
16
|
let response;
|
|
15
17
|
if (loginApi) {
|
|
16
|
-
// 실제 API 호출
|
|
17
18
|
response = await loginApi(request);
|
|
18
19
|
}
|
|
19
20
|
else {
|
|
@@ -34,8 +35,8 @@ export function useLogin(loginApi) {
|
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
37
|
// Store에 저장
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
store.dispatch(setAccessToken(response.accessToken));
|
|
39
|
+
store.dispatch(setUser(response.user));
|
|
39
40
|
// Storage에도 저장 (새로고침 대비)
|
|
40
41
|
storage.setAccessToken(response.accessToken);
|
|
41
42
|
storage.setUser(response.user);
|
|
@@ -54,21 +55,20 @@ export function useLogin(loginApi) {
|
|
|
54
55
|
export function useLogout(logoutApi) {
|
|
55
56
|
return useCallback(async () => {
|
|
56
57
|
try {
|
|
58
|
+
const store = getStore();
|
|
57
59
|
// API 호출 (있는 경우)
|
|
58
60
|
if (logoutApi) {
|
|
59
61
|
await logoutApi();
|
|
60
62
|
}
|
|
61
63
|
// Store 초기화
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
dispatchToHost({ type: 'recentMenu/resetRecentMenu' });
|
|
64
|
+
store.dispatch(logout());
|
|
65
|
+
store.dispatch({ type: 'recentMenu/resetRecentMenu' });
|
|
65
66
|
// Storage 초기화
|
|
66
67
|
storage.clearAuth();
|
|
67
68
|
console.log('[Logout] 로그아웃 완료');
|
|
68
69
|
}
|
|
69
70
|
catch (error) {
|
|
70
71
|
console.error('[Logout] 로그아웃 실패:', error);
|
|
71
|
-
// 에러가 발생해도 로컬 상태는 초기화
|
|
72
72
|
storage.clearAuth();
|
|
73
73
|
throw error;
|
|
74
74
|
}
|
|
@@ -80,14 +80,14 @@ export function useLogout(logoutApi) {
|
|
|
80
80
|
export function useTokenRefresh(refreshApi) {
|
|
81
81
|
return useCallback(async () => {
|
|
82
82
|
try {
|
|
83
|
+
const store = getStore();
|
|
83
84
|
if (!refreshApi) {
|
|
84
85
|
console.warn('[Token Refresh] refresh API가 설정되지 않았습니다.');
|
|
85
86
|
return null;
|
|
86
87
|
}
|
|
87
88
|
const newToken = await refreshApi();
|
|
88
89
|
if (newToken) {
|
|
89
|
-
|
|
90
|
-
dispatchToHost({ type: 'app/setAccessToken', payload: newToken });
|
|
90
|
+
store.dispatch(setAccessToken(newToken));
|
|
91
91
|
storage.setAccessToken(newToken);
|
|
92
92
|
console.log('[Token Refresh] 토큰 갱신 성공');
|
|
93
93
|
return newToken;
|
|
@@ -96,8 +96,8 @@ export function useTokenRefresh(refreshApi) {
|
|
|
96
96
|
}
|
|
97
97
|
catch (error) {
|
|
98
98
|
console.error('[Token Refresh] 토큰 갱신 실패:', error);
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
const store = getStore();
|
|
100
|
+
store.dispatch(setAccessToken(''));
|
|
101
101
|
storage.setAccessToken('');
|
|
102
102
|
throw error;
|
|
103
103
|
}
|
|
@@ -105,13 +105,14 @@ export function useTokenRefresh(refreshApi) {
|
|
|
105
105
|
}
|
|
106
106
|
/**
|
|
107
107
|
* 인증 상태 확인 Hook
|
|
108
|
+
* useSelector를 사용하여 상태 변경시 리렌더링 보장
|
|
108
109
|
*/
|
|
109
110
|
export function useAuthState() {
|
|
110
|
-
const
|
|
111
|
-
const
|
|
111
|
+
const accessToken = useSelector(selectAccessToken);
|
|
112
|
+
const user = useSelector(selectUser);
|
|
112
113
|
return {
|
|
113
|
-
isAuthenticated: !!
|
|
114
|
-
user
|
|
115
|
-
accessToken
|
|
114
|
+
isAuthenticated: !!accessToken,
|
|
115
|
+
user,
|
|
116
|
+
accessToken,
|
|
116
117
|
};
|
|
117
118
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useDebounce / useThrottle Hooks
|
|
3
|
+
* 입력 지연 및 스로틀링을 위한 훅
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* useDebounce Hook
|
|
7
|
+
* 값이 변경된 후 지정된 시간이 지난 뒤에 업데이트
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* const [search, setSearch] = useState('');
|
|
11
|
+
* const debouncedSearch = useDebounce(search, 300);
|
|
12
|
+
*
|
|
13
|
+
* useEffect(() => {
|
|
14
|
+
* // debouncedSearch 값이 변경될 때만 API 호출
|
|
15
|
+
* fetchSearchResults(debouncedSearch);
|
|
16
|
+
* }, [debouncedSearch]);
|
|
17
|
+
*/
|
|
18
|
+
export declare function useDebounce<T>(value: T, delay?: number): T;
|
|
19
|
+
/**
|
|
20
|
+
* useDebouncedCallback Hook
|
|
21
|
+
* 콜백 함수를 디바운스 처리
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* const handleSearch = useDebouncedCallback((query: string) => {
|
|
25
|
+
* fetchSearchResults(query);
|
|
26
|
+
* }, 300);
|
|
27
|
+
*
|
|
28
|
+
* <input onChange={(e) => handleSearch(e.target.value)} />
|
|
29
|
+
*/
|
|
30
|
+
export declare function useDebouncedCallback<T extends (...args: any[]) => any>(callback: T, delay?: number): (...args: Parameters<T>) => void;
|
|
31
|
+
/**
|
|
32
|
+
* useThrottle Hook
|
|
33
|
+
* 값 업데이트를 지정된 간격으로 제한
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* const [scrollY, setScrollY] = useState(0);
|
|
37
|
+
* const throttledScrollY = useThrottle(scrollY, 100);
|
|
38
|
+
*/
|
|
39
|
+
export declare function useThrottle<T>(value: T, interval?: number): T;
|
|
40
|
+
/**
|
|
41
|
+
* useThrottledCallback Hook
|
|
42
|
+
* 콜백 함수를 스로틀 처리
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* const handleScroll = useThrottledCallback(() => {
|
|
46
|
+
* console.log('Scrolling...', window.scrollY);
|
|
47
|
+
* }, 100);
|
|
48
|
+
*
|
|
49
|
+
* useEffect(() => {
|
|
50
|
+
* window.addEventListener('scroll', handleScroll);
|
|
51
|
+
* return () => window.removeEventListener('scroll', handleScroll);
|
|
52
|
+
* }, [handleScroll]);
|
|
53
|
+
*/
|
|
54
|
+
export declare function useThrottledCallback<T extends (...args: any[]) => any>(callback: T, interval?: number): (...args: Parameters<T>) => void;
|
|
55
|
+
export default useDebounce;
|
|
56
|
+
//# sourceMappingURL=use-debounce.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-debounce.d.ts","sourceRoot":"","sources":["../../src/hooks/use-debounce.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,GAAE,MAAY,GAAG,CAAC,CAc/D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,EACpE,QAAQ,EAAE,CAAC,EACX,KAAK,GAAE,MAAY,GAClB,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,IAAI,CA8BlC;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,GAAE,MAAY,GAAG,CAAC,CAsBlE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,EACpE,QAAQ,EAAE,CAAC,EACX,QAAQ,GAAE,MAAY,GACrB,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,IAAI,CAqClC;AAED,eAAe,WAAW,CAAC"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useDebounce / useThrottle Hooks
|
|
3
|
+
* 입력 지연 및 스로틀링을 위한 훅
|
|
4
|
+
*/
|
|
5
|
+
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
6
|
+
/**
|
|
7
|
+
* useDebounce Hook
|
|
8
|
+
* 값이 변경된 후 지정된 시간이 지난 뒤에 업데이트
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* const [search, setSearch] = useState('');
|
|
12
|
+
* const debouncedSearch = useDebounce(search, 300);
|
|
13
|
+
*
|
|
14
|
+
* useEffect(() => {
|
|
15
|
+
* // debouncedSearch 값이 변경될 때만 API 호출
|
|
16
|
+
* fetchSearchResults(debouncedSearch);
|
|
17
|
+
* }, [debouncedSearch]);
|
|
18
|
+
*/
|
|
19
|
+
export function useDebounce(value, delay = 300) {
|
|
20
|
+
const [debouncedValue, setDebouncedValue] = useState(value);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
const timer = setTimeout(() => {
|
|
23
|
+
setDebouncedValue(value);
|
|
24
|
+
}, delay);
|
|
25
|
+
return () => {
|
|
26
|
+
clearTimeout(timer);
|
|
27
|
+
};
|
|
28
|
+
}, [value, delay]);
|
|
29
|
+
return debouncedValue;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* useDebouncedCallback Hook
|
|
33
|
+
* 콜백 함수를 디바운스 처리
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* const handleSearch = useDebouncedCallback((query: string) => {
|
|
37
|
+
* fetchSearchResults(query);
|
|
38
|
+
* }, 300);
|
|
39
|
+
*
|
|
40
|
+
* <input onChange={(e) => handleSearch(e.target.value)} />
|
|
41
|
+
*/
|
|
42
|
+
export function useDebouncedCallback(callback, delay = 300) {
|
|
43
|
+
const timeoutRef = useRef(null);
|
|
44
|
+
const callbackRef = useRef(callback);
|
|
45
|
+
// 콜백 참조 업데이트
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
callbackRef.current = callback;
|
|
48
|
+
}, [callback]);
|
|
49
|
+
// 클린업
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
return () => {
|
|
52
|
+
if (timeoutRef.current) {
|
|
53
|
+
clearTimeout(timeoutRef.current);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}, []);
|
|
57
|
+
return useCallback((...args) => {
|
|
58
|
+
if (timeoutRef.current) {
|
|
59
|
+
clearTimeout(timeoutRef.current);
|
|
60
|
+
}
|
|
61
|
+
timeoutRef.current = setTimeout(() => {
|
|
62
|
+
callbackRef.current(...args);
|
|
63
|
+
}, delay);
|
|
64
|
+
}, [delay]);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* useThrottle Hook
|
|
68
|
+
* 값 업데이트를 지정된 간격으로 제한
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* const [scrollY, setScrollY] = useState(0);
|
|
72
|
+
* const throttledScrollY = useThrottle(scrollY, 100);
|
|
73
|
+
*/
|
|
74
|
+
export function useThrottle(value, interval = 300) {
|
|
75
|
+
const [throttledValue, setThrottledValue] = useState(value);
|
|
76
|
+
const lastExecuted = useRef(Date.now());
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
const now = Date.now();
|
|
79
|
+
const timeSinceLastExec = now - lastExecuted.current;
|
|
80
|
+
if (timeSinceLastExec >= interval) {
|
|
81
|
+
lastExecuted.current = now;
|
|
82
|
+
setThrottledValue(value);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
const timer = setTimeout(() => {
|
|
86
|
+
lastExecuted.current = Date.now();
|
|
87
|
+
setThrottledValue(value);
|
|
88
|
+
}, interval - timeSinceLastExec);
|
|
89
|
+
return () => clearTimeout(timer);
|
|
90
|
+
}
|
|
91
|
+
}, [value, interval]);
|
|
92
|
+
return throttledValue;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* useThrottledCallback Hook
|
|
96
|
+
* 콜백 함수를 스로틀 처리
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* const handleScroll = useThrottledCallback(() => {
|
|
100
|
+
* console.log('Scrolling...', window.scrollY);
|
|
101
|
+
* }, 100);
|
|
102
|
+
*
|
|
103
|
+
* useEffect(() => {
|
|
104
|
+
* window.addEventListener('scroll', handleScroll);
|
|
105
|
+
* return () => window.removeEventListener('scroll', handleScroll);
|
|
106
|
+
* }, [handleScroll]);
|
|
107
|
+
*/
|
|
108
|
+
export function useThrottledCallback(callback, interval = 300) {
|
|
109
|
+
const lastExecuted = useRef(0);
|
|
110
|
+
const timeoutRef = useRef(null);
|
|
111
|
+
const callbackRef = useRef(callback);
|
|
112
|
+
// 콜백 참조 업데이트
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
callbackRef.current = callback;
|
|
115
|
+
}, [callback]);
|
|
116
|
+
// 클린업
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
return () => {
|
|
119
|
+
if (timeoutRef.current) {
|
|
120
|
+
clearTimeout(timeoutRef.current);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
}, []);
|
|
124
|
+
return useCallback((...args) => {
|
|
125
|
+
const now = Date.now();
|
|
126
|
+
const timeSinceLastExec = now - lastExecuted.current;
|
|
127
|
+
if (timeSinceLastExec >= interval) {
|
|
128
|
+
lastExecuted.current = now;
|
|
129
|
+
callbackRef.current(...args);
|
|
130
|
+
}
|
|
131
|
+
else if (!timeoutRef.current) {
|
|
132
|
+
timeoutRef.current = setTimeout(() => {
|
|
133
|
+
lastExecuted.current = Date.now();
|
|
134
|
+
callbackRef.current(...args);
|
|
135
|
+
timeoutRef.current = null;
|
|
136
|
+
}, interval - timeSinceLastExec);
|
|
137
|
+
}
|
|
138
|
+
}, [interval]);
|
|
139
|
+
}
|
|
140
|
+
export default useDebounce;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useEffectOnce Hook
|
|
3
|
+
* 조건이 충족되면 한 번만 실행되는 useEffect
|
|
4
|
+
* KOMCA 패턴 업그레이드 버전
|
|
5
|
+
*/
|
|
6
|
+
import { DependencyList } from 'react';
|
|
7
|
+
/**
|
|
8
|
+
* useEffectOnce
|
|
9
|
+
* ready 조건이 true가 되면 한 번만 실행
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* // 기본 사용
|
|
13
|
+
* useEffectOnce(() => {
|
|
14
|
+
* console.log('한 번만 실행');
|
|
15
|
+
* }, isLoaded);
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* // cleanup 함수 포함
|
|
19
|
+
* useEffectOnce(() => {
|
|
20
|
+
* const subscription = subscribe();
|
|
21
|
+
* return () => subscription.unsubscribe();
|
|
22
|
+
* }, isReady);
|
|
23
|
+
*/
|
|
24
|
+
export declare function useEffectOnce(effect: () => (() => void) | void, ready?: boolean): void;
|
|
25
|
+
/**
|
|
26
|
+
* useMount
|
|
27
|
+
* 컴포넌트 마운트 시 한 번만 실행
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* useMount(() => {
|
|
31
|
+
* analytics.pageView();
|
|
32
|
+
* });
|
|
33
|
+
*/
|
|
34
|
+
export declare function useMount(effect: () => (() => void) | void): void;
|
|
35
|
+
/**
|
|
36
|
+
* useUnmount
|
|
37
|
+
* 컴포넌트 언마운트 시 실행
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* useUnmount(() => {
|
|
41
|
+
* cleanup();
|
|
42
|
+
* });
|
|
43
|
+
*/
|
|
44
|
+
export declare function useUnmount(cleanup: () => void): void;
|
|
45
|
+
/**
|
|
46
|
+
* useUpdateEffect
|
|
47
|
+
* 첫 렌더링을 제외하고 deps가 변경될 때만 실행
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* useUpdateEffect(() => {
|
|
51
|
+
* console.log('count가 변경됨 (첫 렌더링 제외)');
|
|
52
|
+
* }, [count]);
|
|
53
|
+
*/
|
|
54
|
+
export declare function useUpdateEffect(effect: () => (() => void) | void, deps: DependencyList): void;
|
|
55
|
+
/**
|
|
56
|
+
* useIsFirstRender
|
|
57
|
+
* 첫 렌더링인지 여부 반환
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* const isFirstRender = useIsFirstRender();
|
|
61
|
+
* if (isFirstRender) {
|
|
62
|
+
* // 첫 렌더링 시에만 실행
|
|
63
|
+
* }
|
|
64
|
+
*/
|
|
65
|
+
export declare function useIsFirstRender(): boolean;
|
|
66
|
+
/**
|
|
67
|
+
* usePrevious
|
|
68
|
+
* 이전 값 반환
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* const [count, setCount] = useState(0);
|
|
72
|
+
* const prevCount = usePrevious(count);
|
|
73
|
+
* // count: 5, prevCount: 4
|
|
74
|
+
*/
|
|
75
|
+
export declare function usePrevious<T>(value: T): T | undefined;
|
|
76
|
+
export default useEffectOnce;
|
|
77
|
+
//# sourceMappingURL=use-effect-once.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-effect-once.d.ts","sourceRoot":"","sources":["../../src/hooks/use-effect-once.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAkC,cAAc,EAAE,MAAM,OAAO,CAAC;AAEvE;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,EACjC,KAAK,GAAE,OAAc,GACpB,IAAI,CAoBN;AAED;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,GAAG,IAAI,CAEhE;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,IAAI,GAAG,IAAI,CAOpD;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,EACjC,IAAI,EAAE,cAAc,GACnB,IAAI,CAYN;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAS1C;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,GAAG,SAAS,CAQtD;AAED,eAAe,aAAa,CAAC"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useEffectOnce Hook
|
|
3
|
+
* 조건이 충족되면 한 번만 실행되는 useEffect
|
|
4
|
+
* KOMCA 패턴 업그레이드 버전
|
|
5
|
+
*/
|
|
6
|
+
import { useEffect, useRef } from 'react';
|
|
7
|
+
/**
|
|
8
|
+
* useEffectOnce
|
|
9
|
+
* ready 조건이 true가 되면 한 번만 실행
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* // 기본 사용
|
|
13
|
+
* useEffectOnce(() => {
|
|
14
|
+
* console.log('한 번만 실행');
|
|
15
|
+
* }, isLoaded);
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* // cleanup 함수 포함
|
|
19
|
+
* useEffectOnce(() => {
|
|
20
|
+
* const subscription = subscribe();
|
|
21
|
+
* return () => subscription.unsubscribe();
|
|
22
|
+
* }, isReady);
|
|
23
|
+
*/
|
|
24
|
+
export function useEffectOnce(effect, ready = true) {
|
|
25
|
+
const hasRun = useRef(false);
|
|
26
|
+
const cleanupRef = useRef(undefined);
|
|
27
|
+
const effectRef = useRef(effect);
|
|
28
|
+
effectRef.current = effect;
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (hasRun.current || !ready) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
hasRun.current = true;
|
|
34
|
+
cleanupRef.current = effectRef.current();
|
|
35
|
+
return () => {
|
|
36
|
+
if (typeof cleanupRef.current === 'function') {
|
|
37
|
+
cleanupRef.current();
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}, [ready]);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* useMount
|
|
44
|
+
* 컴포넌트 마운트 시 한 번만 실행
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* useMount(() => {
|
|
48
|
+
* analytics.pageView();
|
|
49
|
+
* });
|
|
50
|
+
*/
|
|
51
|
+
export function useMount(effect) {
|
|
52
|
+
useEffectOnce(effect, true);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* useUnmount
|
|
56
|
+
* 컴포넌트 언마운트 시 실행
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* useUnmount(() => {
|
|
60
|
+
* cleanup();
|
|
61
|
+
* });
|
|
62
|
+
*/
|
|
63
|
+
export function useUnmount(cleanup) {
|
|
64
|
+
const cleanupRef = useRef(cleanup);
|
|
65
|
+
cleanupRef.current = cleanup;
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
return () => cleanupRef.current();
|
|
68
|
+
}, []);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* useUpdateEffect
|
|
72
|
+
* 첫 렌더링을 제외하고 deps가 변경될 때만 실행
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* useUpdateEffect(() => {
|
|
76
|
+
* console.log('count가 변경됨 (첫 렌더링 제외)');
|
|
77
|
+
* }, [count]);
|
|
78
|
+
*/
|
|
79
|
+
export function useUpdateEffect(effect, deps) {
|
|
80
|
+
const isFirstMount = useRef(true);
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (isFirstMount.current) {
|
|
83
|
+
isFirstMount.current = false;
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
return effect();
|
|
87
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
88
|
+
}, deps);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* useIsFirstRender
|
|
92
|
+
* 첫 렌더링인지 여부 반환
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* const isFirstRender = useIsFirstRender();
|
|
96
|
+
* if (isFirstRender) {
|
|
97
|
+
* // 첫 렌더링 시에만 실행
|
|
98
|
+
* }
|
|
99
|
+
*/
|
|
100
|
+
export function useIsFirstRender() {
|
|
101
|
+
const isFirst = useRef(true);
|
|
102
|
+
if (isFirst.current) {
|
|
103
|
+
isFirst.current = false;
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* usePrevious
|
|
110
|
+
* 이전 값 반환
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* const [count, setCount] = useState(0);
|
|
114
|
+
* const prevCount = usePrevious(count);
|
|
115
|
+
* // count: 5, prevCount: 4
|
|
116
|
+
*/
|
|
117
|
+
export function usePrevious(value) {
|
|
118
|
+
const ref = useRef(undefined);
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
ref.current = value;
|
|
121
|
+
}, [value]);
|
|
122
|
+
return ref.current;
|
|
123
|
+
}
|
|
124
|
+
export default useEffectOnce;
|