@aws-amplify/ui-react-core 3.4.1 → 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/index.mjs +1 -1
- package/dist/index.js +75 -45
- package/dist/types/hooks/index.d.ts +1 -1
- package/dist/types/hooks/useAsyncReducer.d.ts +55 -0
- package/dist/types/index.d.ts +1 -1
- package/package.json +3 -3
- package/src/hooks/index.ts +5 -7
- package/src/hooks/useAsyncReducer.ts +111 -0
- package/src/index.ts +5 -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 };
|
package/dist/esm/index.mjs
CHANGED
|
@@ -9,9 +9,9 @@ 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 useDataState } from './hooks/useDataState/useDataState.mjs';
|
|
13
12
|
export { default as useControlledReducer } from './hooks/useControlledReducer.mjs';
|
|
14
13
|
export { default as useDropZone } from './hooks/useDropZone.mjs';
|
|
14
|
+
export { default as useAsyncReducer } from './hooks/useAsyncReducer.mjs';
|
|
15
15
|
export { default as useDeprecationWarning } from './hooks/useDeprecationWarning.mjs';
|
|
16
16
|
export { default as useGetUrl } from './hooks/useGetUrl.mjs';
|
|
17
17
|
export { default as useHasValueUpdated } from './hooks/useHasValueUpdated.mjs';
|
package/dist/index.js
CHANGED
|
@@ -605,50 +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
608
|
function usePreviousValue(value) {
|
|
653
609
|
const previous = React.useRef(undefined);
|
|
654
610
|
// update ref post render
|
|
@@ -868,6 +824,80 @@ function useDropZone({ onDropComplete, onDragEnter: _onDragEnter, onDragLeave: _
|
|
|
868
824
|
};
|
|
869
825
|
}
|
|
870
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
|
+
|
|
871
901
|
/**
|
|
872
902
|
* Logs a deprecation warning message.
|
|
873
903
|
*
|
|
@@ -1031,11 +1061,11 @@ exports.RenderNothing = RenderNothing;
|
|
|
1031
1061
|
exports.createContextUtilities = createContextUtilities;
|
|
1032
1062
|
exports.isAuthenticatorComponentRouteKey = isComponentRouteKey;
|
|
1033
1063
|
exports.resolveAuthenticatorComponents = resolveAuthenticatorComponents;
|
|
1064
|
+
exports.useAsyncReducer = useAsyncReducer;
|
|
1034
1065
|
exports.useAuthenticator = useAuthenticator;
|
|
1035
1066
|
exports.useAuthenticatorInitMachine = useAuthenticatorInitMachine;
|
|
1036
1067
|
exports.useAuthenticatorRoute = useAuthenticatorRoute;
|
|
1037
1068
|
exports.useControlledReducer = useControlledReducer;
|
|
1038
|
-
exports.useDataState = useDataState;
|
|
1039
1069
|
exports.useDeprecationWarning = useDeprecationWarning;
|
|
1040
1070
|
exports.useDropZone = useDropZone;
|
|
1041
1071
|
exports.useField = useField;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { useDataState, AsyncDataAction, DataAction, DataState, } from './useDataState';
|
|
2
1
|
export { default as useControlledReducer } from './useControlledReducer';
|
|
3
2
|
export { default as useDropZone, UseDropZoneParams } from './useDropZone';
|
|
3
|
+
export { default as useAsyncReducer, AsyncReducer, AsyncReducerState, } from './useAsyncReducer';
|
|
4
4
|
export { default as useDeprecationWarning, UseDeprecationWarning, } from './useDeprecationWarning';
|
|
5
5
|
export { default as useGetUrl } from './useGetUrl';
|
|
6
6
|
export { default as useHasValueUpdated } from './useHasValueUpdated';
|
|
@@ -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>];
|
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,12 +1,10 @@
|
|
|
1
|
-
export {
|
|
2
|
-
useDataState,
|
|
3
|
-
AsyncDataAction,
|
|
4
|
-
DataAction,
|
|
5
|
-
DataState,
|
|
6
|
-
} from './useDataState';
|
|
7
|
-
|
|
8
1
|
export { default as useControlledReducer } from './useControlledReducer';
|
|
9
2
|
export { default as useDropZone, UseDropZoneParams } from './useDropZone';
|
|
3
|
+
export {
|
|
4
|
+
default as useAsyncReducer,
|
|
5
|
+
AsyncReducer,
|
|
6
|
+
AsyncReducerState,
|
|
7
|
+
} from './useAsyncReducer';
|
|
10
8
|
export {
|
|
11
9
|
default as useDeprecationWarning,
|
|
12
10
|
UseDeprecationWarning,
|
|
@@ -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
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -35,20 +35,19 @@ export {
|
|
|
35
35
|
} from './components';
|
|
36
36
|
|
|
37
37
|
export {
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
AsyncReducer,
|
|
39
|
+
AsyncReducerState,
|
|
40
|
+
useAsyncReducer,
|
|
40
41
|
useControlledReducer,
|
|
41
42
|
useDeprecationWarning,
|
|
42
43
|
UseDeprecationWarning,
|
|
44
|
+
useDropZone,
|
|
45
|
+
UseDropZoneParams,
|
|
43
46
|
useGetUrl,
|
|
44
47
|
useHasValueUpdated,
|
|
45
48
|
usePreviousValue,
|
|
46
49
|
useSetUserAgent,
|
|
47
50
|
useTimeout,
|
|
48
|
-
useDataState,
|
|
49
|
-
DataState,
|
|
50
|
-
useDropZone,
|
|
51
|
-
UseDropZoneParams,
|
|
52
51
|
} from './hooks';
|
|
53
52
|
|
|
54
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
|
-
}
|