@etsoo/react 1.1.85 → 1.1.89

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.
@@ -1,3 +1,4 @@
1
+ import Done from '@mui/icons-material/Done';
1
2
  import React from 'react';
2
3
  import ReactDOM from 'react-dom';
3
4
  import { act } from 'react-dom/test-utils';
@@ -33,7 +34,11 @@ test('Alert tests', () => {
33
34
 
34
35
  expect(button.length).toBe(1);
35
36
  expect(button[0].innerHTML).toContain('OK');
36
- button[0].click();
37
+
38
+ act(() => {
39
+ button[0].click();
40
+ });
41
+
37
42
  expect(handleClick).toBeCalled();
38
43
 
39
44
  // Fast forward
@@ -54,7 +59,11 @@ test('Confirm tests', () => {
54
59
 
55
60
  expect(button.length).toBe(2);
56
61
  expect(button[0].innerHTML).toContain('Cancel');
57
- button[0].click();
62
+
63
+ act(() => {
64
+ button[0].click();
65
+ });
66
+
58
67
  expect(handleClick).toBeCalled();
59
68
 
60
69
  // Fast forward
@@ -63,11 +72,13 @@ test('Confirm tests', () => {
63
72
 
64
73
  test('Prompt tests', () => {
65
74
  // Click
66
- const handleClick = jest.fn();
75
+ const handleClick = jest.fn((result: boolean) => {
76
+ expect(result).toBeTruthy();
77
+ });
67
78
 
68
79
  act(() => {
69
80
  // Add the notification
70
- notifier.prompt('Prompt message', handleClick, undefined, {
81
+ notifier.prompt<boolean>('Prompt message', handleClick, undefined, {
71
82
  type: 'switch'
72
83
  });
73
84
  });
@@ -77,10 +88,13 @@ test('Prompt tests', () => {
77
88
 
78
89
  expect(button.length).toBe(2); // Switch will generate a button
79
90
  expect(button[1].innerHTML).toContain('OK');
80
- button[1].click();
91
+
92
+ act(() => {
93
+ button[1].click();
94
+ });
95
+
81
96
  expect(handleClick).toBeCalled();
82
97
 
83
- // Fast forward
84
98
  jest.runOnlyPendingTimers();
85
99
  });
86
100
 
@@ -8,6 +8,9 @@ import { UserAction, UserState } from '../states/UserState';
8
8
  import { InputDialogProps } from './InputDialogProps';
9
9
  /**
10
10
  * React app state detector
11
+ * Case 1: undefined, when refresh the whole page
12
+ * Case 2: false, unauthorized
13
+ * Case 3: true, authorized or considered as authorized (maynot, like token expiry)
11
14
  * @param props Props
12
15
  * @returns Component
13
16
  */
@@ -5,6 +5,9 @@ import { UserActionType, UserState } from '../states/UserState';
5
5
  let app;
6
6
  /**
7
7
  * React app state detector
8
+ * Case 1: undefined, when refresh the whole page
9
+ * Case 2: false, unauthorized
10
+ * Case 3: true, authorized or considered as authorized (maynot, like token expiry)
8
11
  * @param props Props
9
12
  * @returns Component
10
13
  */
@@ -95,10 +95,6 @@ export interface GridLoaderStates<T> extends GridLoadDataProps {
95
95
  * Is next page loading?
96
96
  */
97
97
  isNextPageLoading: boolean;
98
- /**
99
- * Mounted or not
100
- */
101
- mounted?: boolean;
102
98
  /**
103
99
  * Current rows
104
100
  */
@@ -10,14 +10,11 @@ export const ScrollerGrid = (props) => {
10
10
  const { autoLoad = true, defaultOrderBy, defaultOrderByAsc, footerRenderer, headerRenderer, itemRenderer, idField = 'id', loadBatchSize, loadData, mRef, onItemsRendered, onSelectChange, rowHeight = 53, threshold = 6, width, ...rest } = props;
11
11
  // States
12
12
  const [state, stateUpdate] = React.useReducer((currentState, newState) => {
13
- if (!currentState.mounted)
14
- return currentState;
15
13
  return { ...currentState, ...newState };
16
14
  }, {
17
15
  autoLoad,
18
16
  currentPage: 0,
19
17
  hasNextPage: true,
20
- mounted: true,
21
18
  isNextPageLoading: false,
22
19
  orderBy: defaultOrderBy,
23
20
  orderByAsc: defaultOrderByAsc,
@@ -25,6 +22,7 @@ export const ScrollerGrid = (props) => {
25
22
  batchSize: 10,
26
23
  selectedItems: []
27
24
  });
25
+ const isMounted = React.useRef(true);
28
26
  const ref = React.createRef();
29
27
  // Load data
30
28
  const loadDataLocal = (pageAdd = 1) => {
@@ -44,7 +42,7 @@ export const ScrollerGrid = (props) => {
44
42
  data
45
43
  };
46
44
  loadData(loadProps).then((result) => {
47
- if (result == null || !state.mounted) {
45
+ if (result == null || !isMounted.current) {
48
46
  return;
49
47
  }
50
48
  const newItems = result.length;
@@ -173,7 +171,7 @@ export const ScrollerGrid = (props) => {
173
171
  }), [state.rows, state.selectedItems]);
174
172
  React.useEffect(() => {
175
173
  return () => {
176
- state.mounted = false;
174
+ isMounted.current = false;
177
175
  };
178
176
  }, []);
179
177
  // Destruct state
@@ -27,14 +27,11 @@ export const ScrollerList = (props) => {
27
27
  const refs = useCombinedRefs(oRef, outerRef);
28
28
  // States
29
29
  const [state, stateUpdate] = React.useReducer((currentState, newState) => {
30
- if (!currentState.mounted)
31
- return currentState;
32
30
  return { ...currentState, ...newState };
33
31
  }, {
34
32
  autoLoad,
35
33
  currentPage: 0,
36
34
  hasNextPage: true,
37
- mounted: true,
38
35
  isNextPageLoading: false,
39
36
  orderBy: defaultOrderBy,
40
37
  orderByAsc: defaultOrderByAsc,
@@ -42,6 +39,7 @@ export const ScrollerList = (props) => {
42
39
  batchSize: GridSizeGet(loadBatchSize, height),
43
40
  selectedItems: []
44
41
  });
42
+ const isMounted = React.useRef(true);
45
43
  // Load data
46
44
  const loadDataLocal = (pageAdd = 1) => {
47
45
  // Prevent multiple loadings
@@ -60,7 +58,7 @@ export const ScrollerList = (props) => {
60
58
  data
61
59
  };
62
60
  loadData(loadProps).then((result) => {
63
- if (result == null || !state.mounted) {
61
+ if (result == null || !isMounted.current) {
64
62
  return;
65
63
  }
66
64
  const newItems = result.length;
@@ -158,7 +156,7 @@ export const ScrollerList = (props) => {
158
156
  window.cancelAnimationFrame(requestAnimationFrameSeed);
159
157
  // Remove scroll event
160
158
  window.removeEventListener('scroll', handleWindowScroll);
161
- state.mounted = false;
159
+ isMounted.current = false;
162
160
  };
163
161
  }, []);
164
162
  // Destruct state
@@ -132,7 +132,7 @@ export class NotificationMU extends NotificationReact {
132
132
  result = this.onReturn(numberValue);
133
133
  }
134
134
  else if (type === 'switch') {
135
- const boolValue = inputRef.current.value == 'true';
135
+ const boolValue = inputRef.current.value === 'true';
136
136
  result = this.onReturn(boolValue);
137
137
  }
138
138
  else {
package/lib/mu/TableEx.js CHANGED
@@ -49,6 +49,7 @@ export function TableEx(props) {
49
49
  batchSize: rowsPerPageLocal,
50
50
  selectedItems: []
51
51
  });
52
+ const isMounted = React.useRef(true);
52
53
  // Reset the state and load again
53
54
  const reset = (add) => {
54
55
  const state = {
@@ -91,7 +92,7 @@ export function TableEx(props) {
91
92
  data
92
93
  };
93
94
  loadData(loadProps).then((result) => {
94
- if (result == null) {
95
+ if (!isMounted.current || result == null) {
95
96
  return;
96
97
  }
97
98
  const newItems = result.length;
@@ -166,6 +167,11 @@ export function TableEx(props) {
166
167
  // Auto load data when current page is 0
167
168
  if (currentPage === 0 && stateAutoLoad && lastLoadedItems == null)
168
169
  loadDataLocal();
170
+ React.useEffect(() => {
171
+ return () => {
172
+ isMounted.current = false;
173
+ };
174
+ }, []);
169
175
  // Layout
170
176
  return (React.createElement(Paper, null,
171
177
  React.createElement(TableContainer, { sx: { maxHeight } },
package/lib/mu/Tiplist.js CHANGED
@@ -27,6 +27,7 @@ export function Tiplist(props) {
27
27
  });
28
28
  // State
29
29
  const [state] = React.useState({});
30
+ const isMounted = React.useRef(true);
30
31
  // Add readOnly
31
32
  const addReadOnly = (params) => {
32
33
  if (readOnly != null) {
@@ -73,6 +74,8 @@ export function Tiplist(props) {
73
74
  stateUpdate({ loading: true });
74
75
  // Load list
75
76
  loadData(keyword, id).then((options) => {
77
+ if (!isMounted.current)
78
+ return;
76
79
  // Indicates loading completed
77
80
  stateUpdate({
78
81
  loading: false,
@@ -107,6 +110,15 @@ export function Tiplist(props) {
107
110
  state.idLoaded = true;
108
111
  }
109
112
  }
113
+ React.useEffect(() => {
114
+ return () => {
115
+ // Clear any seed
116
+ if (state.loadDataSeed != null) {
117
+ clearTimeout(state.loadDataSeed);
118
+ }
119
+ isMounted.current = false;
120
+ };
121
+ }, []);
110
122
  // Layout
111
123
  return (React.createElement("div", null,
112
124
  React.createElement("input", { ref: inputRef, "data-reset": "true", type: "text", style: { display: 'none' }, name: name, defaultValue: localIdValue }),
@@ -22,7 +22,7 @@ export const CommonPageScrollContainer = global;
22
22
  */
23
23
  export function CommonPage(props) {
24
24
  // Destruct
25
- const { children, disableGutters = true, fabButtons, fabSize = 'medium', maxWidth = false, moreActions, onRefresh, onUpdate, paddings = MUGlobal.pagePaddings, scrollContainer, pullContainer, sx = {}, ...rest } = props;
25
+ const { children, disableGutters = true, fabButtons, fabSize = 'medium', maxWidth = false, moreActions, onRefresh, onUpdate, onUpdateAll, paddings = MUGlobal.pagePaddings, scrollContainer, pullContainer, sx = {}, ...rest } = props;
26
26
  // Merge style
27
27
  Object.assign(sx, {
28
28
  padding: paddings
@@ -30,14 +30,19 @@ export function CommonPage(props) {
30
30
  // Labels
31
31
  const labels = Labels.CommonPage;
32
32
  // Update
33
- const update = onUpdate
34
- ? onUpdate
35
- : onRefresh
33
+ const update = onUpdateAll
34
+ ? onUpdateAll
35
+ : onUpdate
36
36
  ? (authorized) => {
37
- if (authorized)
38
- onRefresh();
37
+ if (authorized == null || authorized)
38
+ onUpdate();
39
39
  }
40
- : undefined;
40
+ : onRefresh
41
+ ? (authorized) => {
42
+ if (authorized)
43
+ onRefresh();
44
+ }
45
+ : undefined;
41
46
  // Return the UI
42
47
  return (React.createElement(React.Fragment, null,
43
48
  update && React.createElement(ReactAppStateDetector, { update: update }),
@@ -21,13 +21,17 @@ export interface CommonPageProps extends Omit<ContainerProps, 'id'> {
21
21
  */
22
22
  moreActions?: MoreAction[];
23
23
  /**
24
- * On refresh callback
24
+ * On refresh callback, only when authorized = true
25
25
  */
26
26
  onRefresh?: () => void | PromiseLike<void>;
27
27
  /**
28
- * On page update, may uses onRefresh
28
+ * On page update, when authorized = null or true case, may uses onRefresh
29
29
  */
30
- onUpdate?: IStateUpdate;
30
+ onUpdate?: () => void | PromiseLike<void>;
31
+ /**
32
+ * On page update, all cases with authorized
33
+ */
34
+ onUpdateAll?: IStateUpdate;
31
35
  /**
32
36
  * Paddings
33
37
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/react",
3
- "version": "1.1.85",
3
+ "version": "1.1.89",
4
4
  "description": "TypeScript ReactJs framework",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -44,11 +44,11 @@
44
44
  "@emotion/react": "^11.4.1",
45
45
  "@emotion/style": "^0.8.0",
46
46
  "@emotion/styled": "^11.3.0",
47
- "@etsoo/appscript": "^1.0.87",
47
+ "@etsoo/appscript": "^1.0.88",
48
48
  "@etsoo/notificationbase": "^1.0.78",
49
49
  "@etsoo/shared": "^1.0.41",
50
- "@mui/icons-material": "^5.0.0-rc.0",
51
- "@mui/material": "^5.0.0-rc.0",
50
+ "@mui/icons-material": "^5.0.0",
51
+ "@mui/material": "^5.0.0",
52
52
  "@reach/router": "^1.3.4",
53
53
  "@types/pica": "^5.1.3",
54
54
  "@types/pulltorefreshjs": "^0.1.5",
@@ -23,6 +23,9 @@ let app: IReactApp<IAppSettings, any>;
23
23
 
24
24
  /**
25
25
  * React app state detector
26
+ * Case 1: undefined, when refresh the whole page
27
+ * Case 2: false, unauthorized
28
+ * Case 3: true, authorized or considered as authorized (maynot, like token expiry)
26
29
  * @param props Props
27
30
  * @returns Component
28
31
  */
@@ -130,11 +130,6 @@ export interface GridLoaderStates<T> extends GridLoadDataProps {
130
130
  */
131
131
  isNextPageLoading: boolean;
132
132
 
133
- /**
134
- * Mounted or not
135
- */
136
- mounted?: boolean;
137
-
138
133
  /**
139
134
  * Current rows
140
135
  */
@@ -164,14 +164,12 @@ export const ScrollerGrid = <T extends Record<string, any>>(
164
164
  currentState: GridLoaderStates<T>,
165
165
  newState: Partial<GridLoaderStates<T>>
166
166
  ) => {
167
- if (!currentState.mounted) return currentState;
168
167
  return { ...currentState, ...newState };
169
168
  },
170
169
  {
171
170
  autoLoad,
172
171
  currentPage: 0,
173
172
  hasNextPage: true,
174
- mounted: true,
175
173
  isNextPageLoading: false,
176
174
  orderBy: defaultOrderBy,
177
175
  orderByAsc: defaultOrderByAsc,
@@ -180,6 +178,7 @@ export const ScrollerGrid = <T extends Record<string, any>>(
180
178
  selectedItems: []
181
179
  }
182
180
  );
181
+ const isMounted = React.useRef(true);
183
182
 
184
183
  const ref = React.createRef<VariableSizeGrid<T>>();
185
184
 
@@ -204,7 +203,7 @@ export const ScrollerGrid = <T extends Record<string, any>>(
204
203
  };
205
204
 
206
205
  loadData(loadProps).then((result) => {
207
- if (result == null || !state.mounted) {
206
+ if (result == null || !isMounted.current) {
208
207
  return;
209
208
  }
210
209
 
@@ -360,7 +359,7 @@ export const ScrollerGrid = <T extends Record<string, any>>(
360
359
 
361
360
  React.useEffect(() => {
362
361
  return () => {
363
- state.mounted = false;
362
+ isMounted.current = false;
364
363
  };
365
364
  }, []);
366
365
 
@@ -142,14 +142,12 @@ export const ScrollerList = <T extends Record<string, any>>(
142
142
  currentState: GridLoaderStates<T>,
143
143
  newState: Partial<GridLoaderStates<T>>
144
144
  ) => {
145
- if (!currentState.mounted) return currentState;
146
145
  return { ...currentState, ...newState };
147
146
  },
148
147
  {
149
148
  autoLoad,
150
149
  currentPage: 0,
151
150
  hasNextPage: true,
152
- mounted: true,
153
151
  isNextPageLoading: false,
154
152
  orderBy: defaultOrderBy,
155
153
  orderByAsc: defaultOrderByAsc,
@@ -158,6 +156,7 @@ export const ScrollerList = <T extends Record<string, any>>(
158
156
  selectedItems: []
159
157
  }
160
158
  );
159
+ const isMounted = React.useRef(true);
161
160
 
162
161
  // Load data
163
162
  const loadDataLocal = (pageAdd: number = 1) => {
@@ -180,7 +179,7 @@ export const ScrollerList = <T extends Record<string, any>>(
180
179
  };
181
180
 
182
181
  loadData(loadProps).then((result) => {
183
- if (result == null || !state.mounted) {
182
+ if (result == null || !isMounted.current) {
184
183
  return;
185
184
  }
186
185
 
@@ -302,7 +301,7 @@ export const ScrollerList = <T extends Record<string, any>>(
302
301
  // Remove scroll event
303
302
  window.removeEventListener('scroll', handleWindowScroll);
304
303
 
305
- state.mounted = false;
304
+ isMounted.current = false;
306
305
  };
307
306
  }, []);
308
307
 
@@ -248,7 +248,7 @@ export class NotificationMU extends NotificationReact {
248
248
  }
249
249
  result = this.onReturn(numberValue);
250
250
  } else if (type === 'switch') {
251
- const boolValue = inputRef.current.value == 'true';
251
+ const boolValue = inputRef.current.value === 'true';
252
252
  result = this.onReturn(boolValue);
253
253
  } else {
254
254
  const textValue = inputRef.current.value.trim();
@@ -163,6 +163,7 @@ export function TableEx<T extends Record<string, any>>(props: TableExProps<T>) {
163
163
  selectedItems: []
164
164
  }
165
165
  );
166
+ const isMounted = React.useRef(true);
166
167
 
167
168
  // Reset the state and load again
168
169
  const reset = (add?: {}) => {
@@ -216,7 +217,7 @@ export function TableEx<T extends Record<string, any>>(props: TableExProps<T>) {
216
217
  };
217
218
 
218
219
  loadData(loadProps).then((result) => {
219
- if (result == null) {
220
+ if (!isMounted.current || result == null) {
220
221
  return;
221
222
  }
222
223
 
@@ -325,6 +326,12 @@ export function TableEx<T extends Record<string, any>>(props: TableExProps<T>) {
325
326
  if (currentPage === 0 && stateAutoLoad && lastLoadedItems == null)
326
327
  loadDataLocal();
327
328
 
329
+ React.useEffect(() => {
330
+ return () => {
331
+ isMounted.current = false;
332
+ };
333
+ }, []);
334
+
328
335
  // Layout
329
336
  return (
330
337
  <Paper>
@@ -113,6 +113,7 @@ export function Tiplist<T extends Record<string, any>>(props: TiplistProps<T>) {
113
113
  idLoaded?: boolean;
114
114
  idSet?: boolean;
115
115
  }>({});
116
+ const isMounted = React.useRef(true);
116
117
 
117
118
  // Add readOnly
118
119
  const addReadOnly = (params: AutocompleteRenderInputParams) => {
@@ -175,6 +176,8 @@ export function Tiplist<T extends Record<string, any>>(props: TiplistProps<T>) {
175
176
 
176
177
  // Load list
177
178
  loadData(keyword, id).then((options) => {
179
+ if (!isMounted.current) return;
180
+
178
181
  // Indicates loading completed
179
182
  stateUpdate({
180
183
  loading: false,
@@ -213,6 +216,17 @@ export function Tiplist<T extends Record<string, any>>(props: TiplistProps<T>) {
213
216
  }
214
217
  }
215
218
 
219
+ React.useEffect(() => {
220
+ return () => {
221
+ // Clear any seed
222
+ if (state.loadDataSeed != null) {
223
+ clearTimeout(state.loadDataSeed);
224
+ }
225
+
226
+ isMounted.current = false;
227
+ };
228
+ }, []);
229
+
216
230
  // Layout
217
231
  return (
218
232
  <div>
@@ -35,6 +35,7 @@ export function CommonPage(props: CommonPageProps) {
35
35
  moreActions,
36
36
  onRefresh,
37
37
  onUpdate,
38
+ onUpdateAll,
38
39
  paddings = MUGlobal.pagePaddings,
39
40
  scrollContainer,
40
41
  pullContainer,
@@ -51,8 +52,12 @@ export function CommonPage(props: CommonPageProps) {
51
52
  const labels = Labels.CommonPage;
52
53
 
53
54
  // Update
54
- const update = onUpdate
55
- ? onUpdate
55
+ const update = onUpdateAll
56
+ ? onUpdateAll
57
+ : onUpdate
58
+ ? (authorized?: boolean) => {
59
+ if (authorized == null || authorized) onUpdate();
60
+ }
56
61
  : onRefresh
57
62
  ? (authorized?: boolean) => {
58
63
  if (authorized) onRefresh();
@@ -24,14 +24,19 @@ export interface CommonPageProps extends Omit<ContainerProps, 'id'> {
24
24
  moreActions?: MoreAction[];
25
25
 
26
26
  /**
27
- * On refresh callback
27
+ * On refresh callback, only when authorized = true
28
28
  */
29
29
  onRefresh?: () => void | PromiseLike<void>;
30
30
 
31
31
  /**
32
- * On page update, may uses onRefresh
32
+ * On page update, when authorized = null or true case, may uses onRefresh
33
33
  */
34
- onUpdate?: IStateUpdate;
34
+ onUpdate?: () => void | PromiseLike<void>;
35
+
36
+ /**
37
+ * On page update, all cases with authorized
38
+ */
39
+ onUpdateAll?: IStateUpdate;
35
40
 
36
41
  /**
37
42
  * Paddings