@aws-amplify/ui-react-core 3.4.0 → 3.4.2
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/esm/Authenticator/hooks/useAuthenticator/useAuthenticator.mjs +1 -1
- package/dist/esm/Authenticator/hooks/useAuthenticator/utils.mjs +1 -1
- package/dist/esm/hooks/useAsyncReducer.mjs +78 -0
- package/dist/esm/hooks/useControlledReducer.mjs +26 -0
- package/dist/esm/index.mjs +3 -2
- package/dist/index.js +171 -119
- package/dist/types/hooks/index.d.ts +3 -2
- package/dist/types/hooks/useAsyncReducer.d.ts +55 -0
- package/dist/types/hooks/useControlledReducer.d.ts +5 -0
- package/dist/types/index.d.ts +1 -1
- package/package.json +3 -3
- package/src/hooks/index.ts +6 -7
- package/src/hooks/useAsyncReducer.ts +111 -0
- package/src/hooks/useControlledReducer.ts +46 -0
- package/src/index.ts +6 -6
- package/dist/esm/hooks/useDataState/useDataState.mjs +0 -48
- package/dist/types/hooks/useDataState/index.d.ts +0 -2
- package/dist/types/hooks/useDataState/types.d.ts +0 -8
- package/dist/types/hooks/useDataState/useDataState.d.ts +0 -8
- package/src/hooks/useDataState/index.ts +0 -2
- package/src/hooks/useDataState/types.ts +0 -13
- package/src/hooks/useDataState/useDataState.ts +0 -70
|
@@ -4,7 +4,7 @@ import { getServiceFacade } from '@aws-amplify/ui';
|
|
|
4
4
|
import 'aws-amplify/auth';
|
|
5
5
|
import { AuthenticatorContext } from '../../context/AuthenticatorContext.mjs';
|
|
6
6
|
import { USE_AUTHENTICATOR_ERROR } from './constants.mjs';
|
|
7
|
-
import {
|
|
7
|
+
import { getComparator, defaultComparator, getQRFields, getMachineFields } from './utils.mjs';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* [📖 Docs](https://ui.docs.amplify.aws/react/connected-components/authenticator/headless#useauthenticator-hook)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getActorContext, getSortedFormFields,
|
|
1
|
+
import { getActorContext, getSortedFormFields, areEmptyArrays, areEmptyObjects, isString, authenticatorTextUtil } from '@aws-amplify/ui';
|
|
2
2
|
import { isComponentRouteKey } from '../utils.mjs';
|
|
3
3
|
|
|
4
4
|
const defaultComparator = () => false;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import React__default from 'react';
|
|
2
|
+
import { isFunction } from '@aws-amplify/ui';
|
|
3
|
+
|
|
4
|
+
// async state constants
|
|
5
|
+
const INITIAL = { hasError: false, isLoading: false, message: undefined };
|
|
6
|
+
const LOADING = { hasError: false, isLoading: true, message: undefined };
|
|
7
|
+
const ERROR = { hasError: true, isLoading: false };
|
|
8
|
+
/**
|
|
9
|
+
* @internal may be updated in future versions
|
|
10
|
+
*
|
|
11
|
+
* @description like `useReducer` but make it async
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* import fetchData from './fetchData';
|
|
16
|
+
*
|
|
17
|
+
* type MyState = { data: string[] | undefined }
|
|
18
|
+
* const initialState: MyState = { data: undefined }
|
|
19
|
+
*
|
|
20
|
+
* type MyAction = { type: 'fetch' | 'clear' }
|
|
21
|
+
*
|
|
22
|
+
* const asyncReducer = async (state: MyState, action: MyAction): Promise<MyState> => {
|
|
23
|
+
* switch(action.type) {
|
|
24
|
+
* case 'fetch':
|
|
25
|
+
* const data = await fetchData();
|
|
26
|
+
* return { data: state.data ? state.data.concat(data) : data }
|
|
27
|
+
* case 'clear':
|
|
28
|
+
* return { data: undefined }
|
|
29
|
+
* }
|
|
30
|
+
* }
|
|
31
|
+
*
|
|
32
|
+
* const FetchDataButton = () => {
|
|
33
|
+
* const [state, dispatch] = useAsyncReducer(asyncReducer, initialState);
|
|
34
|
+
*
|
|
35
|
+
* const { value: { data }, isLoading } = state;
|
|
36
|
+
*
|
|
37
|
+
* return (
|
|
38
|
+
* <button onClick={() => isLoading ? null : dispatch({ type: 'fetch'})}>
|
|
39
|
+
* Fetch Data
|
|
40
|
+
* </button>
|
|
41
|
+
* )
|
|
42
|
+
* }
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
function useAsyncReducer(reducer, initialValue, options) {
|
|
46
|
+
const [state, setAsyncState] = React__default.useState(() => ({
|
|
47
|
+
...INITIAL,
|
|
48
|
+
value: initialValue,
|
|
49
|
+
}));
|
|
50
|
+
const prevValue = React__default.useRef(initialValue);
|
|
51
|
+
const pendingId = React__default.useRef();
|
|
52
|
+
const { onSuccess, onError } = options ?? {};
|
|
53
|
+
const dispatch = React__default.useCallback((input) => {
|
|
54
|
+
const id = Symbol();
|
|
55
|
+
pendingId.current = id;
|
|
56
|
+
setAsyncState(({ value }) => ({ ...LOADING, value }));
|
|
57
|
+
reducer(prevValue.current, input)
|
|
58
|
+
.then((value) => {
|
|
59
|
+
if (pendingId.current !== id)
|
|
60
|
+
return;
|
|
61
|
+
prevValue.current = value;
|
|
62
|
+
if (isFunction(onSuccess))
|
|
63
|
+
onSuccess(value);
|
|
64
|
+
setAsyncState({ ...INITIAL, value });
|
|
65
|
+
})
|
|
66
|
+
.catch((error) => {
|
|
67
|
+
if (pendingId.current !== id)
|
|
68
|
+
return;
|
|
69
|
+
if (isFunction(onError))
|
|
70
|
+
onError(error);
|
|
71
|
+
const { message } = error;
|
|
72
|
+
setAsyncState(({ value }) => ({ ...ERROR, value, message }));
|
|
73
|
+
});
|
|
74
|
+
}, [onError, onSuccess, reducer]);
|
|
75
|
+
return [state, dispatch];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export { useAsyncReducer as default };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React__default from 'react';
|
|
2
|
+
import { isUndefined } from '@aws-amplify/ui';
|
|
3
|
+
import useHasValueUpdated from './useHasValueUpdated.mjs';
|
|
4
|
+
|
|
5
|
+
function useControlledReducer(reducer, initialState, options) {
|
|
6
|
+
const { controlledState, onStateChange } = options ?? {};
|
|
7
|
+
const [uncontrolledState, dispatch] = React__default.useReducer(reducer, controlledState ?? initialState);
|
|
8
|
+
const controlledStateRef = React__default.useRef();
|
|
9
|
+
if (!isUndefined(controlledState)) {
|
|
10
|
+
controlledStateRef.current = controlledState;
|
|
11
|
+
}
|
|
12
|
+
const hasUncontrolledStateChanged = useHasValueUpdated(uncontrolledState, true);
|
|
13
|
+
React__default.useEffect(() => {
|
|
14
|
+
// only run `onStateChange` if `uncontrolledState` has changed,
|
|
15
|
+
// ignore reference change to `onStateChange`
|
|
16
|
+
if (hasUncontrolledStateChanged) {
|
|
17
|
+
onStateChange?.(uncontrolledState);
|
|
18
|
+
}
|
|
19
|
+
}, [hasUncontrolledStateChanged, onStateChange, uncontrolledState]);
|
|
20
|
+
const state = controlledStateRef.current
|
|
21
|
+
? controlledStateRef.current
|
|
22
|
+
: uncontrolledState;
|
|
23
|
+
return React__default.useMemo(() => [state, dispatch], [state]);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { useControlledReducer as default };
|
package/dist/esm/index.mjs
CHANGED
|
@@ -9,12 +9,13 @@ export { default as useForm } from './components/FormCore/useForm.mjs';
|
|
|
9
9
|
export { default as FormProvider } from './components/FormCore/FormProvider.mjs';
|
|
10
10
|
export { default as withFormProvider } from './components/FormCore/withFormProvider.mjs';
|
|
11
11
|
export { default as RenderNothing } from './components/RenderNothing/RenderNothing.mjs';
|
|
12
|
-
export { default as
|
|
12
|
+
export { default as useControlledReducer } from './hooks/useControlledReducer.mjs';
|
|
13
|
+
export { default as useDropZone } from './hooks/useDropZone.mjs';
|
|
14
|
+
export { default as useAsyncReducer } from './hooks/useAsyncReducer.mjs';
|
|
13
15
|
export { default as useDeprecationWarning } from './hooks/useDeprecationWarning.mjs';
|
|
14
16
|
export { default as useGetUrl } from './hooks/useGetUrl.mjs';
|
|
15
17
|
export { default as useHasValueUpdated } from './hooks/useHasValueUpdated.mjs';
|
|
16
18
|
export { default as usePreviousValue } from './hooks/usePreviousValue.mjs';
|
|
17
19
|
export { default as useSetUserAgent } from './hooks/useSetUserAgent.mjs';
|
|
18
20
|
export { default as useTimeout } from './hooks/useTimeout.mjs';
|
|
19
|
-
export { default as useDropZone } from './hooks/useDropZone.mjs';
|
|
20
21
|
export { default as createContextUtilities } from './utils/createContextUtilities.mjs';
|
package/dist/index.js
CHANGED
|
@@ -605,99 +605,6 @@ function useAuthenticatorInitMachine(data) {
|
|
|
605
605
|
}, [initializeMachine, route, data]);
|
|
606
606
|
}
|
|
607
607
|
|
|
608
|
-
// default state
|
|
609
|
-
const INITIAL_STATE = { hasError: false, isLoading: false, message: undefined };
|
|
610
|
-
const LOADING_STATE = { hasError: false, isLoading: true, message: undefined };
|
|
611
|
-
const ERROR_STATE = { hasError: true, isLoading: false };
|
|
612
|
-
const resolveMaybeAsync = async (value) => {
|
|
613
|
-
const awaited = await value;
|
|
614
|
-
return awaited;
|
|
615
|
-
};
|
|
616
|
-
/**
|
|
617
|
-
* @internal may be updated in future versions
|
|
618
|
-
*/
|
|
619
|
-
function useDataState(action, initialData, options) {
|
|
620
|
-
const [dataState, setDataState] = React__namespace["default"].useState(() => ({
|
|
621
|
-
...INITIAL_STATE,
|
|
622
|
-
data: initialData,
|
|
623
|
-
}));
|
|
624
|
-
const prevData = React__namespace["default"].useRef(initialData);
|
|
625
|
-
const pendingId = React__namespace["default"].useRef();
|
|
626
|
-
const { onSuccess, onError } = options ?? {};
|
|
627
|
-
const handleAction = React__namespace["default"].useCallback((input) => {
|
|
628
|
-
const id = Symbol();
|
|
629
|
-
pendingId.current = id;
|
|
630
|
-
setDataState(({ data }) => ({ ...LOADING_STATE, data }));
|
|
631
|
-
resolveMaybeAsync(action(prevData.current, input))
|
|
632
|
-
.then((data) => {
|
|
633
|
-
if (pendingId.current !== id)
|
|
634
|
-
return;
|
|
635
|
-
prevData.current = data;
|
|
636
|
-
if (ui.isFunction(onSuccess))
|
|
637
|
-
onSuccess(data);
|
|
638
|
-
setDataState({ ...INITIAL_STATE, data });
|
|
639
|
-
})
|
|
640
|
-
.catch((error) => {
|
|
641
|
-
if (pendingId.current !== id)
|
|
642
|
-
return;
|
|
643
|
-
if (ui.isFunction(onError))
|
|
644
|
-
onError(error);
|
|
645
|
-
const { message } = error;
|
|
646
|
-
setDataState(({ data }) => ({ ...ERROR_STATE, data, message }));
|
|
647
|
-
});
|
|
648
|
-
}, [action, onError, onSuccess]);
|
|
649
|
-
return [dataState, handleAction];
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
/**
|
|
653
|
-
* Logs a deprecation warning message.
|
|
654
|
-
*
|
|
655
|
-
* @important Please use the React/React Native specific platform implementations.
|
|
656
|
-
* This version of the hook is a base implementation that the others extend from due
|
|
657
|
-
* to env differences between running in RN or the browser
|
|
658
|
-
*/
|
|
659
|
-
const useDeprecationWarning = ({ shouldWarn, message, }) => {
|
|
660
|
-
React__namespace.useEffect(() => {
|
|
661
|
-
if (shouldWarn) {
|
|
662
|
-
// eslint-disable-next-line no-console
|
|
663
|
-
console.warn(message);
|
|
664
|
-
}
|
|
665
|
-
}, [shouldWarn, message]);
|
|
666
|
-
};
|
|
667
|
-
|
|
668
|
-
const INIT_STATE = {
|
|
669
|
-
url: undefined,
|
|
670
|
-
expiresAt: undefined,
|
|
671
|
-
isLoading: true,
|
|
672
|
-
};
|
|
673
|
-
function useGetUrl(input) {
|
|
674
|
-
const [result, setResult] = React__namespace.useState(() => INIT_STATE);
|
|
675
|
-
React__namespace.useEffect(() => {
|
|
676
|
-
const { onError, ...getUrlInput } = input;
|
|
677
|
-
let ignore = false;
|
|
678
|
-
storage.getUrl(getUrlInput)
|
|
679
|
-
.then((response) => {
|
|
680
|
-
if (ignore) {
|
|
681
|
-
return;
|
|
682
|
-
}
|
|
683
|
-
setResult({ ...response, isLoading: false });
|
|
684
|
-
})
|
|
685
|
-
.catch((error) => {
|
|
686
|
-
if (ignore) {
|
|
687
|
-
return;
|
|
688
|
-
}
|
|
689
|
-
if (ui.isFunction(onError)) {
|
|
690
|
-
onError(error);
|
|
691
|
-
}
|
|
692
|
-
setResult({ ...INIT_STATE, isLoading: false });
|
|
693
|
-
});
|
|
694
|
-
return () => {
|
|
695
|
-
ignore = true;
|
|
696
|
-
};
|
|
697
|
-
}, [input]);
|
|
698
|
-
return result;
|
|
699
|
-
}
|
|
700
|
-
|
|
701
608
|
function usePreviousValue(value) {
|
|
702
609
|
const previous = React.useRef(undefined);
|
|
703
610
|
// update ref post render
|
|
@@ -730,33 +637,25 @@ function useHasValueUpdated(value, ignoreFirstRender = false) {
|
|
|
730
637
|
return previous !== value;
|
|
731
638
|
}
|
|
732
639
|
|
|
733
|
-
function
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
}, [componentName, packageName, version]);
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
function useTimeout({ callback, delay, }) {
|
|
745
|
-
const storedCallback = React__namespace["default"].useRef(callback);
|
|
746
|
-
React__namespace["default"].useLayoutEffect(() => {
|
|
747
|
-
storedCallback.current = callback;
|
|
748
|
-
}, [callback]);
|
|
640
|
+
function useControlledReducer(reducer, initialState, options) {
|
|
641
|
+
const { controlledState, onStateChange } = options ?? {};
|
|
642
|
+
const [uncontrolledState, dispatch] = React__namespace["default"].useReducer(reducer, controlledState ?? initialState);
|
|
643
|
+
const controlledStateRef = React__namespace["default"].useRef();
|
|
644
|
+
if (!ui.isUndefined(controlledState)) {
|
|
645
|
+
controlledStateRef.current = controlledState;
|
|
646
|
+
}
|
|
647
|
+
const hasUncontrolledStateChanged = useHasValueUpdated(uncontrolledState, true);
|
|
749
648
|
React__namespace["default"].useEffect(() => {
|
|
750
|
-
if
|
|
751
|
-
|
|
649
|
+
// only run `onStateChange` if `uncontrolledState` has changed,
|
|
650
|
+
// ignore reference change to `onStateChange`
|
|
651
|
+
if (hasUncontrolledStateChanged) {
|
|
652
|
+
onStateChange?.(uncontrolledState);
|
|
752
653
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
};
|
|
759
|
-
}, [delay]);
|
|
654
|
+
}, [hasUncontrolledStateChanged, onStateChange, uncontrolledState]);
|
|
655
|
+
const state = controlledStateRef.current
|
|
656
|
+
? controlledStateRef.current
|
|
657
|
+
: uncontrolledState;
|
|
658
|
+
return React__namespace["default"].useMemo(() => [state, dispatch], [state]);
|
|
760
659
|
}
|
|
761
660
|
|
|
762
661
|
function filterAllowedFiles(files, acceptedFileTypes) {
|
|
@@ -925,6 +824,158 @@ function useDropZone({ onDropComplete, onDragEnter: _onDragEnter, onDragLeave: _
|
|
|
925
824
|
};
|
|
926
825
|
}
|
|
927
826
|
|
|
827
|
+
// async state constants
|
|
828
|
+
const INITIAL = { hasError: false, isLoading: false, message: undefined };
|
|
829
|
+
const LOADING = { hasError: false, isLoading: true, message: undefined };
|
|
830
|
+
const ERROR = { hasError: true, isLoading: false };
|
|
831
|
+
/**
|
|
832
|
+
* @internal may be updated in future versions
|
|
833
|
+
*
|
|
834
|
+
* @description like `useReducer` but make it async
|
|
835
|
+
*
|
|
836
|
+
* @example
|
|
837
|
+
* ```ts
|
|
838
|
+
* import fetchData from './fetchData';
|
|
839
|
+
*
|
|
840
|
+
* type MyState = { data: string[] | undefined }
|
|
841
|
+
* const initialState: MyState = { data: undefined }
|
|
842
|
+
*
|
|
843
|
+
* type MyAction = { type: 'fetch' | 'clear' }
|
|
844
|
+
*
|
|
845
|
+
* const asyncReducer = async (state: MyState, action: MyAction): Promise<MyState> => {
|
|
846
|
+
* switch(action.type) {
|
|
847
|
+
* case 'fetch':
|
|
848
|
+
* const data = await fetchData();
|
|
849
|
+
* return { data: state.data ? state.data.concat(data) : data }
|
|
850
|
+
* case 'clear':
|
|
851
|
+
* return { data: undefined }
|
|
852
|
+
* }
|
|
853
|
+
* }
|
|
854
|
+
*
|
|
855
|
+
* const FetchDataButton = () => {
|
|
856
|
+
* const [state, dispatch] = useAsyncReducer(asyncReducer, initialState);
|
|
857
|
+
*
|
|
858
|
+
* const { value: { data }, isLoading } = state;
|
|
859
|
+
*
|
|
860
|
+
* return (
|
|
861
|
+
* <button onClick={() => isLoading ? null : dispatch({ type: 'fetch'})}>
|
|
862
|
+
* Fetch Data
|
|
863
|
+
* </button>
|
|
864
|
+
* )
|
|
865
|
+
* }
|
|
866
|
+
* ```
|
|
867
|
+
*/
|
|
868
|
+
function useAsyncReducer(reducer, initialValue, options) {
|
|
869
|
+
const [state, setAsyncState] = React__namespace["default"].useState(() => ({
|
|
870
|
+
...INITIAL,
|
|
871
|
+
value: initialValue,
|
|
872
|
+
}));
|
|
873
|
+
const prevValue = React__namespace["default"].useRef(initialValue);
|
|
874
|
+
const pendingId = React__namespace["default"].useRef();
|
|
875
|
+
const { onSuccess, onError } = options ?? {};
|
|
876
|
+
const dispatch = React__namespace["default"].useCallback((input) => {
|
|
877
|
+
const id = Symbol();
|
|
878
|
+
pendingId.current = id;
|
|
879
|
+
setAsyncState(({ value }) => ({ ...LOADING, value }));
|
|
880
|
+
reducer(prevValue.current, input)
|
|
881
|
+
.then((value) => {
|
|
882
|
+
if (pendingId.current !== id)
|
|
883
|
+
return;
|
|
884
|
+
prevValue.current = value;
|
|
885
|
+
if (ui.isFunction(onSuccess))
|
|
886
|
+
onSuccess(value);
|
|
887
|
+
setAsyncState({ ...INITIAL, value });
|
|
888
|
+
})
|
|
889
|
+
.catch((error) => {
|
|
890
|
+
if (pendingId.current !== id)
|
|
891
|
+
return;
|
|
892
|
+
if (ui.isFunction(onError))
|
|
893
|
+
onError(error);
|
|
894
|
+
const { message } = error;
|
|
895
|
+
setAsyncState(({ value }) => ({ ...ERROR, value, message }));
|
|
896
|
+
});
|
|
897
|
+
}, [onError, onSuccess, reducer]);
|
|
898
|
+
return [state, dispatch];
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
/**
|
|
902
|
+
* Logs a deprecation warning message.
|
|
903
|
+
*
|
|
904
|
+
* @important Please use the React/React Native specific platform implementations.
|
|
905
|
+
* This version of the hook is a base implementation that the others extend from due
|
|
906
|
+
* to env differences between running in RN or the browser
|
|
907
|
+
*/
|
|
908
|
+
const useDeprecationWarning = ({ shouldWarn, message, }) => {
|
|
909
|
+
React__namespace.useEffect(() => {
|
|
910
|
+
if (shouldWarn) {
|
|
911
|
+
// eslint-disable-next-line no-console
|
|
912
|
+
console.warn(message);
|
|
913
|
+
}
|
|
914
|
+
}, [shouldWarn, message]);
|
|
915
|
+
};
|
|
916
|
+
|
|
917
|
+
const INIT_STATE = {
|
|
918
|
+
url: undefined,
|
|
919
|
+
expiresAt: undefined,
|
|
920
|
+
isLoading: true,
|
|
921
|
+
};
|
|
922
|
+
function useGetUrl(input) {
|
|
923
|
+
const [result, setResult] = React__namespace.useState(() => INIT_STATE);
|
|
924
|
+
React__namespace.useEffect(() => {
|
|
925
|
+
const { onError, ...getUrlInput } = input;
|
|
926
|
+
let ignore = false;
|
|
927
|
+
storage.getUrl(getUrlInput)
|
|
928
|
+
.then((response) => {
|
|
929
|
+
if (ignore) {
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
setResult({ ...response, isLoading: false });
|
|
933
|
+
})
|
|
934
|
+
.catch((error) => {
|
|
935
|
+
if (ignore) {
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
if (ui.isFunction(onError)) {
|
|
939
|
+
onError(error);
|
|
940
|
+
}
|
|
941
|
+
setResult({ ...INIT_STATE, isLoading: false });
|
|
942
|
+
});
|
|
943
|
+
return () => {
|
|
944
|
+
ignore = true;
|
|
945
|
+
};
|
|
946
|
+
}, [input]);
|
|
947
|
+
return result;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
function useSetUserAgent({ componentName, packageName, version, }) {
|
|
951
|
+
React.useEffect(() => {
|
|
952
|
+
const clearUserAgent = ui.setUserAgent({
|
|
953
|
+
componentName,
|
|
954
|
+
packageName,
|
|
955
|
+
version,
|
|
956
|
+
});
|
|
957
|
+
return clearUserAgent;
|
|
958
|
+
}, [componentName, packageName, version]);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
function useTimeout({ callback, delay, }) {
|
|
962
|
+
const storedCallback = React__namespace["default"].useRef(callback);
|
|
963
|
+
React__namespace["default"].useLayoutEffect(() => {
|
|
964
|
+
storedCallback.current = callback;
|
|
965
|
+
}, [callback]);
|
|
966
|
+
React__namespace["default"].useEffect(() => {
|
|
967
|
+
if (!ui.isFunction(storedCallback.current) || !delay) {
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
const timeoutId = setTimeout(() => {
|
|
971
|
+
storedCallback.current?.();
|
|
972
|
+
}, delay);
|
|
973
|
+
return () => {
|
|
974
|
+
clearTimeout(timeoutId);
|
|
975
|
+
};
|
|
976
|
+
}, [delay]);
|
|
977
|
+
}
|
|
978
|
+
|
|
928
979
|
const INVALID_OPTIONS_MESSAGE = 'an `errorMessage` or a `defaultValue` must be provided in `options`';
|
|
929
980
|
/**
|
|
930
981
|
* Uses `ContextType`/`Name` generics and `options` to create:
|
|
@@ -1010,10 +1061,11 @@ exports.RenderNothing = RenderNothing;
|
|
|
1010
1061
|
exports.createContextUtilities = createContextUtilities;
|
|
1011
1062
|
exports.isAuthenticatorComponentRouteKey = isComponentRouteKey;
|
|
1012
1063
|
exports.resolveAuthenticatorComponents = resolveAuthenticatorComponents;
|
|
1064
|
+
exports.useAsyncReducer = useAsyncReducer;
|
|
1013
1065
|
exports.useAuthenticator = useAuthenticator;
|
|
1014
1066
|
exports.useAuthenticatorInitMachine = useAuthenticatorInitMachine;
|
|
1015
1067
|
exports.useAuthenticatorRoute = useAuthenticatorRoute;
|
|
1016
|
-
exports.
|
|
1068
|
+
exports.useControlledReducer = useControlledReducer;
|
|
1017
1069
|
exports.useDeprecationWarning = useDeprecationWarning;
|
|
1018
1070
|
exports.useDropZone = useDropZone;
|
|
1019
1071
|
exports.useField = useField;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { default as useControlledReducer } from './useControlledReducer';
|
|
2
|
+
export { default as useDropZone, UseDropZoneParams } from './useDropZone';
|
|
3
|
+
export { default as useAsyncReducer, AsyncReducer, AsyncReducerState, } from './useAsyncReducer';
|
|
2
4
|
export { default as useDeprecationWarning, UseDeprecationWarning, } from './useDeprecationWarning';
|
|
3
5
|
export { default as useGetUrl } from './useGetUrl';
|
|
4
6
|
export { default as useHasValueUpdated } from './useHasValueUpdated';
|
|
5
7
|
export { default as usePreviousValue } from './usePreviousValue';
|
|
6
8
|
export { default as useSetUserAgent } from './useSetUserAgent';
|
|
7
9
|
export { default as useTimeout } from './useTimeout';
|
|
8
|
-
export { default as useDropZone, UseDropZoneParams } from './useDropZone';
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface AsyncReducerState<T> {
|
|
3
|
+
/**
|
|
4
|
+
* current value
|
|
5
|
+
*/
|
|
6
|
+
value: T;
|
|
7
|
+
hasError: boolean;
|
|
8
|
+
isLoading: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* error message, if any
|
|
11
|
+
*/
|
|
12
|
+
message: string | undefined;
|
|
13
|
+
}
|
|
14
|
+
export type AsyncReducer<S, A> = (prevValue: S, action: A) => Promise<S>;
|
|
15
|
+
/**
|
|
16
|
+
* @internal may be updated in future versions
|
|
17
|
+
*
|
|
18
|
+
* @description like `useReducer` but make it async
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* import fetchData from './fetchData';
|
|
23
|
+
*
|
|
24
|
+
* type MyState = { data: string[] | undefined }
|
|
25
|
+
* const initialState: MyState = { data: undefined }
|
|
26
|
+
*
|
|
27
|
+
* type MyAction = { type: 'fetch' | 'clear' }
|
|
28
|
+
*
|
|
29
|
+
* const asyncReducer = async (state: MyState, action: MyAction): Promise<MyState> => {
|
|
30
|
+
* switch(action.type) {
|
|
31
|
+
* case 'fetch':
|
|
32
|
+
* const data = await fetchData();
|
|
33
|
+
* return { data: state.data ? state.data.concat(data) : data }
|
|
34
|
+
* case 'clear':
|
|
35
|
+
* return { data: undefined }
|
|
36
|
+
* }
|
|
37
|
+
* }
|
|
38
|
+
*
|
|
39
|
+
* const FetchDataButton = () => {
|
|
40
|
+
* const [state, dispatch] = useAsyncReducer(asyncReducer, initialState);
|
|
41
|
+
*
|
|
42
|
+
* const { value: { data }, isLoading } = state;
|
|
43
|
+
*
|
|
44
|
+
* return (
|
|
45
|
+
* <button onClick={() => isLoading ? null : dispatch({ type: 'fetch'})}>
|
|
46
|
+
* Fetch Data
|
|
47
|
+
* </button>
|
|
48
|
+
* )
|
|
49
|
+
* }
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export default function useAsyncReducer<T, K>(reducer: AsyncReducer<T, K>, initialValue: T, options?: {
|
|
53
|
+
onSuccess?: (data: T) => void;
|
|
54
|
+
onError?: (error: Error) => void;
|
|
55
|
+
}): [AsyncReducerState<T>, React.Dispatch<K>];
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export default function useControlledReducer<R extends React.Reducer<any, any>, S extends React.ReducerState<R>>(reducer: R, initialState: S, options?: {
|
|
3
|
+
controlledState?: S;
|
|
4
|
+
onStateChange?: (state: S) => void;
|
|
5
|
+
}): [S, React.Dispatch<React.ReducerAction<R>>];
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { AuthenticatorComponentDefaults, AuthenticatorComponentDefaultProps, AuthenticatorComponentOverrides, AuthenticatorFooterComponent, AuthenticatorFormFieldsComponent, AuthenticatorHeaderComponent, AuthenticatorLegacyField, AuthenticatorMachineContext, AuthenticatorProvider, AuthenticatorRouteComponentKey, AuthenticatorRouteComponentName, isAuthenticatorComponentRouteKey, resolveAuthenticatorComponents, useAuthenticator, useAuthenticatorRoute, UseAuthenticator, useAuthenticatorInitMachine, UseAuthenticatorRoute, } from './Authenticator';
|
|
2
2
|
export { FormProvider, FormProviderProps, RenderNothing, FormValues, FormHandle, useField, useForm, UseForm, Validate, Validator, withFormProvider, } from './components';
|
|
3
|
-
export {
|
|
3
|
+
export { AsyncReducer, AsyncReducerState, useAsyncReducer, useControlledReducer, useDeprecationWarning, UseDeprecationWarning, useDropZone, UseDropZoneParams, useGetUrl, useHasValueUpdated, usePreviousValue, useSetUserAgent, useTimeout, } from './hooks';
|
|
4
4
|
export { MergeProps } from './types';
|
|
5
5
|
export { createContextUtilities } from './utils';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aws-amplify/ui-react-core",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.2",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"module": "dist/esm/index.mjs",
|
|
6
6
|
"react-native": "src/index.ts",
|
|
@@ -40,14 +40,14 @@
|
|
|
40
40
|
"typecheck": "tsc --noEmit"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@aws-amplify/ui": "6.10.
|
|
43
|
+
"@aws-amplify/ui": "6.10.2",
|
|
44
44
|
"@xstate/react": "^3.2.2",
|
|
45
45
|
"lodash": "4.17.21",
|
|
46
46
|
"react-hook-form": "^7.53.2",
|
|
47
47
|
"xstate": "^4.33.6"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
|
-
"aws-amplify": "^6.
|
|
50
|
+
"aws-amplify": "^6.14.3",
|
|
51
51
|
"react": "^16.14 || ^17 || ^18 || ^19"
|
|
52
52
|
},
|
|
53
53
|
"sideEffects": false
|
package/src/hooks/index.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
export { default as useControlledReducer } from './useControlledReducer';
|
|
2
|
+
export { default as useDropZone, UseDropZoneParams } from './useDropZone';
|
|
1
3
|
export {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
} from './useDataState';
|
|
7
|
-
|
|
4
|
+
default as useAsyncReducer,
|
|
5
|
+
AsyncReducer,
|
|
6
|
+
AsyncReducerState,
|
|
7
|
+
} from './useAsyncReducer';
|
|
8
8
|
export {
|
|
9
9
|
default as useDeprecationWarning,
|
|
10
10
|
UseDeprecationWarning,
|
|
@@ -14,4 +14,3 @@ export { default as useHasValueUpdated } from './useHasValueUpdated';
|
|
|
14
14
|
export { default as usePreviousValue } from './usePreviousValue';
|
|
15
15
|
export { default as useSetUserAgent } from './useSetUserAgent';
|
|
16
16
|
export { default as useTimeout } from './useTimeout';
|
|
17
|
-
export { default as useDropZone, UseDropZoneParams } from './useDropZone';
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { isFunction } from '@aws-amplify/ui';
|
|
4
|
+
|
|
5
|
+
// async state constants
|
|
6
|
+
const INITIAL = { hasError: false, isLoading: false, message: undefined };
|
|
7
|
+
const LOADING = { hasError: false, isLoading: true, message: undefined };
|
|
8
|
+
const ERROR = { hasError: true, isLoading: false };
|
|
9
|
+
|
|
10
|
+
export interface AsyncReducerState<T> {
|
|
11
|
+
/**
|
|
12
|
+
* current value
|
|
13
|
+
*/
|
|
14
|
+
value: T;
|
|
15
|
+
hasError: boolean;
|
|
16
|
+
isLoading: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* error message, if any
|
|
19
|
+
*/
|
|
20
|
+
message: string | undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type AsyncReducer<S, A> = (prevValue: S, action: A) => Promise<S>;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @internal may be updated in future versions
|
|
27
|
+
*
|
|
28
|
+
* @description like `useReducer` but make it async
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* import fetchData from './fetchData';
|
|
33
|
+
*
|
|
34
|
+
* type MyState = { data: string[] | undefined }
|
|
35
|
+
* const initialState: MyState = { data: undefined }
|
|
36
|
+
*
|
|
37
|
+
* type MyAction = { type: 'fetch' | 'clear' }
|
|
38
|
+
*
|
|
39
|
+
* const asyncReducer = async (state: MyState, action: MyAction): Promise<MyState> => {
|
|
40
|
+
* switch(action.type) {
|
|
41
|
+
* case 'fetch':
|
|
42
|
+
* const data = await fetchData();
|
|
43
|
+
* return { data: state.data ? state.data.concat(data) : data }
|
|
44
|
+
* case 'clear':
|
|
45
|
+
* return { data: undefined }
|
|
46
|
+
* }
|
|
47
|
+
* }
|
|
48
|
+
*
|
|
49
|
+
* const FetchDataButton = () => {
|
|
50
|
+
* const [state, dispatch] = useAsyncReducer(asyncReducer, initialState);
|
|
51
|
+
*
|
|
52
|
+
* const { value: { data }, isLoading } = state;
|
|
53
|
+
*
|
|
54
|
+
* return (
|
|
55
|
+
* <button onClick={() => isLoading ? null : dispatch({ type: 'fetch'})}>
|
|
56
|
+
* Fetch Data
|
|
57
|
+
* </button>
|
|
58
|
+
* )
|
|
59
|
+
* }
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export default function useAsyncReducer<T, K>(
|
|
63
|
+
reducer: AsyncReducer<T, K>,
|
|
64
|
+
initialValue: T,
|
|
65
|
+
options?: {
|
|
66
|
+
onSuccess?: (data: T) => void;
|
|
67
|
+
onError?: (error: Error) => void;
|
|
68
|
+
}
|
|
69
|
+
): [AsyncReducerState<T>, React.Dispatch<K>] {
|
|
70
|
+
const [state, setAsyncState] = React.useState<AsyncReducerState<T>>(() => ({
|
|
71
|
+
...INITIAL,
|
|
72
|
+
value: initialValue,
|
|
73
|
+
}));
|
|
74
|
+
|
|
75
|
+
const prevValue = React.useRef(initialValue);
|
|
76
|
+
const pendingId = React.useRef<Symbol | undefined>();
|
|
77
|
+
|
|
78
|
+
const { onSuccess, onError } = options ?? {};
|
|
79
|
+
|
|
80
|
+
const dispatch: React.Dispatch<K> = React.useCallback(
|
|
81
|
+
(input) => {
|
|
82
|
+
const id = Symbol();
|
|
83
|
+
pendingId.current = id;
|
|
84
|
+
|
|
85
|
+
setAsyncState(({ value }) => ({ ...LOADING, value }));
|
|
86
|
+
|
|
87
|
+
reducer(prevValue.current, input)
|
|
88
|
+
.then((value: T) => {
|
|
89
|
+
if (pendingId.current !== id) return;
|
|
90
|
+
|
|
91
|
+
prevValue.current = value;
|
|
92
|
+
|
|
93
|
+
if (isFunction(onSuccess)) onSuccess(value);
|
|
94
|
+
|
|
95
|
+
setAsyncState({ ...INITIAL, value });
|
|
96
|
+
})
|
|
97
|
+
.catch((error: Error) => {
|
|
98
|
+
if (pendingId.current !== id) return;
|
|
99
|
+
|
|
100
|
+
if (isFunction(onError)) onError(error);
|
|
101
|
+
|
|
102
|
+
const { message } = error;
|
|
103
|
+
|
|
104
|
+
setAsyncState(({ value }) => ({ ...ERROR, value, message }));
|
|
105
|
+
});
|
|
106
|
+
},
|
|
107
|
+
[onError, onSuccess, reducer]
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
return [state, dispatch];
|
|
111
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { isUndefined } from '@aws-amplify/ui';
|
|
3
|
+
|
|
4
|
+
import useHasValueUpdated from './useHasValueUpdated';
|
|
5
|
+
|
|
6
|
+
export default function useControlledReducer<
|
|
7
|
+
R extends React.Reducer<any, any>,
|
|
8
|
+
S extends React.ReducerState<R>,
|
|
9
|
+
>(
|
|
10
|
+
reducer: R,
|
|
11
|
+
initialState: S,
|
|
12
|
+
options?: {
|
|
13
|
+
controlledState?: S;
|
|
14
|
+
onStateChange?: (state: S) => void;
|
|
15
|
+
}
|
|
16
|
+
): [S, React.Dispatch<React.ReducerAction<R>>] {
|
|
17
|
+
const { controlledState, onStateChange } = options ?? {};
|
|
18
|
+
|
|
19
|
+
const [uncontrolledState, dispatch] = React.useReducer(
|
|
20
|
+
reducer,
|
|
21
|
+
controlledState ?? initialState
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const controlledStateRef = React.useRef<S | undefined>();
|
|
25
|
+
if (!isUndefined(controlledState)) {
|
|
26
|
+
controlledStateRef.current = controlledState;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const hasUncontrolledStateChanged = useHasValueUpdated(
|
|
30
|
+
uncontrolledState,
|
|
31
|
+
true
|
|
32
|
+
);
|
|
33
|
+
React.useEffect(() => {
|
|
34
|
+
// only run `onStateChange` if `uncontrolledState` has changed,
|
|
35
|
+
// ignore reference change to `onStateChange`
|
|
36
|
+
if (hasUncontrolledStateChanged) {
|
|
37
|
+
onStateChange?.(uncontrolledState);
|
|
38
|
+
}
|
|
39
|
+
}, [hasUncontrolledStateChanged, onStateChange, uncontrolledState]);
|
|
40
|
+
|
|
41
|
+
const state = controlledStateRef.current
|
|
42
|
+
? controlledStateRef.current
|
|
43
|
+
: uncontrolledState;
|
|
44
|
+
|
|
45
|
+
return React.useMemo(() => [state, dispatch], [state]);
|
|
46
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -35,19 +35,19 @@ export {
|
|
|
35
35
|
} from './components';
|
|
36
36
|
|
|
37
37
|
export {
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
AsyncReducer,
|
|
39
|
+
AsyncReducerState,
|
|
40
|
+
useAsyncReducer,
|
|
41
|
+
useControlledReducer,
|
|
40
42
|
useDeprecationWarning,
|
|
41
43
|
UseDeprecationWarning,
|
|
44
|
+
useDropZone,
|
|
45
|
+
UseDropZoneParams,
|
|
42
46
|
useGetUrl,
|
|
43
47
|
useHasValueUpdated,
|
|
44
48
|
usePreviousValue,
|
|
45
49
|
useSetUserAgent,
|
|
46
50
|
useTimeout,
|
|
47
|
-
useDataState,
|
|
48
|
-
DataState,
|
|
49
|
-
useDropZone,
|
|
50
|
-
UseDropZoneParams,
|
|
51
51
|
} from './hooks';
|
|
52
52
|
|
|
53
53
|
export { MergeProps } from './types';
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import React__default from 'react';
|
|
2
|
-
import { isFunction } from '@aws-amplify/ui';
|
|
3
|
-
|
|
4
|
-
// default state
|
|
5
|
-
const INITIAL_STATE = { hasError: false, isLoading: false, message: undefined };
|
|
6
|
-
const LOADING_STATE = { hasError: false, isLoading: true, message: undefined };
|
|
7
|
-
const ERROR_STATE = { hasError: true, isLoading: false };
|
|
8
|
-
const resolveMaybeAsync = async (value) => {
|
|
9
|
-
const awaited = await value;
|
|
10
|
-
return awaited;
|
|
11
|
-
};
|
|
12
|
-
/**
|
|
13
|
-
* @internal may be updated in future versions
|
|
14
|
-
*/
|
|
15
|
-
function useDataState(action, initialData, options) {
|
|
16
|
-
const [dataState, setDataState] = React__default.useState(() => ({
|
|
17
|
-
...INITIAL_STATE,
|
|
18
|
-
data: initialData,
|
|
19
|
-
}));
|
|
20
|
-
const prevData = React__default.useRef(initialData);
|
|
21
|
-
const pendingId = React__default.useRef();
|
|
22
|
-
const { onSuccess, onError } = options ?? {};
|
|
23
|
-
const handleAction = React__default.useCallback((input) => {
|
|
24
|
-
const id = Symbol();
|
|
25
|
-
pendingId.current = id;
|
|
26
|
-
setDataState(({ data }) => ({ ...LOADING_STATE, data }));
|
|
27
|
-
resolveMaybeAsync(action(prevData.current, input))
|
|
28
|
-
.then((data) => {
|
|
29
|
-
if (pendingId.current !== id)
|
|
30
|
-
return;
|
|
31
|
-
prevData.current = data;
|
|
32
|
-
if (isFunction(onSuccess))
|
|
33
|
-
onSuccess(data);
|
|
34
|
-
setDataState({ ...INITIAL_STATE, data });
|
|
35
|
-
})
|
|
36
|
-
.catch((error) => {
|
|
37
|
-
if (pendingId.current !== id)
|
|
38
|
-
return;
|
|
39
|
-
if (isFunction(onError))
|
|
40
|
-
onError(error);
|
|
41
|
-
const { message } = error;
|
|
42
|
-
setDataState(({ data }) => ({ ...ERROR_STATE, data, message }));
|
|
43
|
-
});
|
|
44
|
-
}, [action, onError, onSuccess]);
|
|
45
|
-
return [dataState, handleAction];
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export { useDataState as default };
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export interface DataState<T> {
|
|
2
|
-
data: T;
|
|
3
|
-
hasError: boolean;
|
|
4
|
-
isLoading: boolean;
|
|
5
|
-
message: string | undefined;
|
|
6
|
-
}
|
|
7
|
-
export type DataAction<T = any, K = any> = (prevData: T, input: K) => T;
|
|
8
|
-
export type AsyncDataAction<T = any, K = any> = (prevData: T, input: K) => Promise<T>;
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { AsyncDataAction, DataAction, DataState } from './types';
|
|
2
|
-
/**
|
|
3
|
-
* @internal may be updated in future versions
|
|
4
|
-
*/
|
|
5
|
-
export default function useDataState<T, K>(action: DataAction<T, K> | AsyncDataAction<T, K>, initialData: T, options?: {
|
|
6
|
-
onSuccess?: (data: T) => void;
|
|
7
|
-
onError?: (error: Error) => void;
|
|
8
|
-
}): [state: DataState<T>, handleAction: (input: K) => void];
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export interface DataState<T> {
|
|
2
|
-
data: T;
|
|
3
|
-
hasError: boolean;
|
|
4
|
-
isLoading: boolean;
|
|
5
|
-
message: string | undefined;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export type DataAction<T = any, K = any> = (prevData: T, input: K) => T;
|
|
9
|
-
|
|
10
|
-
export type AsyncDataAction<T = any, K = any> = (
|
|
11
|
-
prevData: T,
|
|
12
|
-
input: K
|
|
13
|
-
) => Promise<T>;
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { isFunction } from '@aws-amplify/ui';
|
|
3
|
-
|
|
4
|
-
import { AsyncDataAction, DataAction, DataState } from './types';
|
|
5
|
-
|
|
6
|
-
// default state
|
|
7
|
-
const INITIAL_STATE = { hasError: false, isLoading: false, message: undefined };
|
|
8
|
-
const LOADING_STATE = { hasError: false, isLoading: true, message: undefined };
|
|
9
|
-
const ERROR_STATE = { hasError: true, isLoading: false };
|
|
10
|
-
|
|
11
|
-
const resolveMaybeAsync = async <T>(
|
|
12
|
-
value: T | Promise<T>
|
|
13
|
-
): Promise<Awaited<T>> => {
|
|
14
|
-
const awaited = await value;
|
|
15
|
-
return awaited;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* @internal may be updated in future versions
|
|
20
|
-
*/
|
|
21
|
-
export default function useDataState<T, K>(
|
|
22
|
-
action: DataAction<T, K> | AsyncDataAction<T, K>,
|
|
23
|
-
initialData: T,
|
|
24
|
-
options?: {
|
|
25
|
-
onSuccess?: (data: T) => void;
|
|
26
|
-
onError?: (error: Error) => void;
|
|
27
|
-
}
|
|
28
|
-
): [state: DataState<T>, handleAction: (input: K) => void] {
|
|
29
|
-
const [dataState, setDataState] = React.useState<DataState<T>>(() => ({
|
|
30
|
-
...INITIAL_STATE,
|
|
31
|
-
data: initialData,
|
|
32
|
-
}));
|
|
33
|
-
|
|
34
|
-
const prevData = React.useRef(initialData);
|
|
35
|
-
const pendingId = React.useRef<Symbol | undefined>();
|
|
36
|
-
|
|
37
|
-
const { onSuccess, onError } = options ?? {};
|
|
38
|
-
|
|
39
|
-
const handleAction: (input: K) => void = React.useCallback(
|
|
40
|
-
(input) => {
|
|
41
|
-
const id = Symbol();
|
|
42
|
-
pendingId.current = id;
|
|
43
|
-
|
|
44
|
-
setDataState(({ data }) => ({ ...LOADING_STATE, data }));
|
|
45
|
-
|
|
46
|
-
resolveMaybeAsync(action(prevData.current, input))
|
|
47
|
-
.then((data: T) => {
|
|
48
|
-
if (pendingId.current !== id) return;
|
|
49
|
-
|
|
50
|
-
prevData.current = data;
|
|
51
|
-
|
|
52
|
-
if (isFunction(onSuccess)) onSuccess(data);
|
|
53
|
-
|
|
54
|
-
setDataState({ ...INITIAL_STATE, data });
|
|
55
|
-
})
|
|
56
|
-
.catch((error: Error) => {
|
|
57
|
-
if (pendingId.current !== id) return;
|
|
58
|
-
|
|
59
|
-
if (isFunction(onError)) onError(error);
|
|
60
|
-
|
|
61
|
-
const { message } = error;
|
|
62
|
-
|
|
63
|
-
setDataState(({ data }) => ({ ...ERROR_STATE, data, message }));
|
|
64
|
-
});
|
|
65
|
-
},
|
|
66
|
-
[action, onError, onSuccess]
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
return [dataState, handleAction];
|
|
70
|
-
}
|