@centreon/ui 23.10.26 → 23.10.27-MON-19397-add-optimistic-ui-on-mutate.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@centreon/ui",
3
- "version": "23.10.26",
3
+ "version": "23.10.27-MON-19397-add-optimistic-ui-on-mutate.0",
4
4
  "description": "Centreon UI Components",
5
5
  "scripts": {
6
6
  "eslint": "eslint ./src --ext .js,.jsx,.ts,.tsx --max-warnings 0",
@@ -58,9 +58,8 @@ const useFetchQuery = <T extends object>({
58
58
  }: UseFetchQueryProps<T>): UseFetchQueryState<T> => {
59
59
  const { showErrorMessage } = useSnackbar();
60
60
 
61
- const queryData = useQuery<T | ResponseError, Error>(
62
- getQueryKey(),
63
- ({ signal }): Promise<T | ResponseError> =>
61
+ const queryData = useQuery<T | ResponseError, Error>({
62
+ queryFn: ({ signal }): Promise<T | ResponseError> =>
64
63
  customFetch<T>({
65
64
  catchError,
66
65
  decoder,
@@ -69,10 +68,9 @@ const useFetchQuery = <T extends object>({
69
68
  headers: new Headers(fetchHeaders),
70
69
  signal
71
70
  }),
72
- {
73
- ...queryOptions
74
- }
75
- );
71
+ queryKey: getQueryKey(),
72
+ ...queryOptions
73
+ });
76
74
 
77
75
  const queryClient = useQueryClient();
78
76
 
@@ -115,4 +115,27 @@ describe('useFetchQuery', () => {
115
115
  expect(mockedShowErrorMessage).not.toHaveBeenCalled();
116
116
  });
117
117
  });
118
+
119
+ it('calls the `onMutate` callback when the mutation has been successfully sent', async () => {
120
+ fetchMock.once(JSON.stringify({}));
121
+ const onMutate = jest.fn();
122
+
123
+ const { result } = renderMutationQuery<User>({
124
+ getEndpoint: () => '/endpoint',
125
+ method: Method.POST,
126
+ optimisticUI: {
127
+ onMutate,
128
+ queryKeyToInvalidate: ['key']
129
+ }
130
+ });
131
+
132
+ result.current.mutate(user);
133
+
134
+ await waitFor(() => {
135
+ expect(onMutate).toHaveBeenCalledWith({
136
+ email: 'john@doe.com',
137
+ name: 'John Doe'
138
+ });
139
+ });
140
+ });
118
141
  });
@@ -1,7 +1,12 @@
1
1
  import 'ulog';
2
- import { useEffect } from 'react';
2
+ import { startTransition, useEffect } from 'react';
3
3
 
4
- import { useMutation } from '@tanstack/react-query';
4
+ import {
5
+ QueryClient,
6
+ QueryKey,
7
+ useMutation,
8
+ useQueryClient
9
+ } from '@tanstack/react-query';
5
10
  import { JsonDecoder } from 'ts.data.json';
6
11
  import anylogger from 'anylogger';
7
12
  import { includes } from 'ramda';
@@ -17,6 +22,19 @@ export enum Method {
17
22
  PUT = 'PUT'
18
23
  }
19
24
 
25
+ interface OptimisticUIProps<T> {
26
+ onError?: (error, variables, context) => void;
27
+ onMutate?: (variables) => unknown;
28
+ onSettled?: (
29
+ data: T | ResponseError | undefined,
30
+ error: unknown,
31
+ variables: void,
32
+ context: unknown
33
+ ) => unknown;
34
+ onSuccess?: (data, variables, context) => void;
35
+ queryKeyToInvalidate: QueryKey;
36
+ }
37
+
20
38
  export interface UseMutationQueryProps<T> {
21
39
  catchError?: (props: CatchErrorProps) => void;
22
40
  decoder?: JsonDecoder.Decoder<T>;
@@ -25,6 +43,7 @@ export interface UseMutationQueryProps<T> {
25
43
  getEndpoint: (payload) => string;
26
44
  httpCodesBypassErrorSnackbar?: Array<number>;
27
45
  method: Method;
46
+ optimisticUI?: OptimisticUIProps<T>;
28
47
  }
29
48
 
30
49
  const log = anylogger('API Request');
@@ -34,6 +53,7 @@ export interface UseMutationQueryState<T> {
34
53
  isMutating: boolean;
35
54
  mutate: (payload) => void;
36
55
  mutateAsync: (payload) => Promise<T | ResponseError>;
56
+ queryClient: QueryClient;
37
57
  }
38
58
 
39
59
  const useMutationQuery = <T extends object>({
@@ -43,12 +63,15 @@ const useMutationQuery = <T extends object>({
43
63
  defaultFailureMessage,
44
64
  fetchHeaders,
45
65
  httpCodesBypassErrorSnackbar = [],
46
- method
66
+ method,
67
+ optimisticUI
47
68
  }: UseMutationQueryProps<T>): UseMutationQueryState<T> => {
48
69
  const { showErrorMessage } = useSnackbar();
49
70
 
50
- const queryData = useMutation<T | ResponseError>(
51
- (payload): Promise<T | ResponseError> =>
71
+ const queryClient = useQueryClient();
72
+
73
+ const queryData = useMutation<T | ResponseError>({
74
+ mutationFn: (payload): Promise<T | ResponseError> =>
52
75
  customFetch<T>({
53
76
  catchError,
54
77
  decoder,
@@ -61,8 +84,20 @@ const useMutationQuery = <T extends object>({
61
84
  isMutation: true,
62
85
  method,
63
86
  payload
64
- })
65
- );
87
+ }),
88
+ onError: optimisticUI?.onError,
89
+ onMutate: optimisticUI?.onMutate,
90
+ onSettled: optimisticUI?.onSettled,
91
+ onSuccess: (data, variables, context) => {
92
+ startTransition(() => {
93
+ queryClient.invalidateQueries({
94
+ queryKey: optimisticUI?.queryKeyToInvalidate
95
+ });
96
+ });
97
+
98
+ optimisticUI?.onSuccess?.(data, variables, context);
99
+ }
100
+ });
66
101
 
67
102
  const manageError = (): void => {
68
103
  const data = queryData.data as ResponseError | undefined;
@@ -87,7 +122,8 @@ const useMutationQuery = <T extends object>({
87
122
  isError: (queryData.data as ResponseError | undefined)?.isError || false,
88
123
  isMutating: queryData.isLoading,
89
124
  mutate: queryData.mutate,
90
- mutateAsync: queryData.mutateAsync
125
+ mutateAsync: queryData.mutateAsync,
126
+ queryClient
91
127
  };
92
128
  };
93
129
 
@@ -0,0 +1,62 @@
1
+ import { useQueryClient } from '@tanstack/react-query';
2
+
3
+ import { ListingModel } from '..';
4
+
5
+ interface ApplyListingMutationProps<T> {
6
+ getNewData: (listing: ListingModel<T>) => ListingModel<T>;
7
+ page: number;
8
+ queryKey: Array<string>;
9
+ }
10
+
11
+ interface RollBackListingMutation {
12
+ context;
13
+ page: number;
14
+ queryKey: Array<string>;
15
+ }
16
+
17
+ interface UseOptimisticListingMutationState<T> {
18
+ applyListingMutation: (props: ApplyListingMutationProps<T>) => Promise<{
19
+ previousListing: ListingModel<T>;
20
+ }>;
21
+ rollBackListingMutation: (props: RollBackListingMutation) => void;
22
+ }
23
+
24
+ const useOptimisticListingMutation = <
25
+ T extends { id: string | number }
26
+ >(): UseOptimisticListingMutationState<T> => {
27
+ const queryClient = useQueryClient();
28
+
29
+ const applyListingMutation = async ({
30
+ page,
31
+ queryKey,
32
+ getNewData
33
+ }: ApplyListingMutationProps<T>): Promise<{
34
+ previousListing: ListingModel<T>;
35
+ }> => {
36
+ await queryClient.cancelQueries({ queryKey });
37
+ const previousListing = queryClient.getQueriesData(
38
+ queryKey
39
+ )[0][1] as ListingModel<T>;
40
+
41
+ const newData = getNewData(previousListing);
42
+
43
+ queryClient.setQueryData([...queryKey, page], newData);
44
+
45
+ return { previousListing };
46
+ };
47
+
48
+ const rollBackListingMutation = ({
49
+ context,
50
+ page,
51
+ queryKey
52
+ }: RollBackListingMutation): void => {
53
+ queryClient.setQueryData([...queryKey, page], context?.previousListing);
54
+ };
55
+
56
+ return {
57
+ applyListingMutation,
58
+ rollBackListingMutation
59
+ };
60
+ };
61
+
62
+ export default useOptimisticListingMutation;
package/src/index.ts CHANGED
@@ -124,6 +124,7 @@ export { default as Responsive } from './Responsive';
124
124
  export { default as useFetchQuery } from './api/useFetchQuery';
125
125
  export { default as useMutationQuery, Method } from './api/useMutationQuery';
126
126
  export { default as QueryProvider } from './api/QueryProvider';
127
+ export { default as useOptimisticListingMutation } from './api/useOptimisticListingMutation';
127
128
  export {
128
129
  default as FileDropZone,
129
130
  transformFileListToArray