@pallasoft/ps-mobile-components 1.0.0 → 1.1.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/dist/hooks/use-ps-remote-data.d.ts +1 -1
- package/dist/hooks/use-ps-remote-data.js +58 -28
- package/dist/interfaces/ps-remote-data-options.d.ts +1 -0
- package/dist/interfaces/ps-remote-data-update.d.ts +1 -0
- package/dist/interfaces/ps-remote-data-update.js +1 -0
- package/dist/interfaces/ps-remote-data-write.d.ts +4 -0
- package/dist/interfaces/ps-remote-data-write.js +1 -0
- package/dist/services/ps-remote-data-service.d.ts +6 -0
- package/dist/services/ps-remote-data-service.js +13 -0
- package/package.json +2 -2
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { PsRemoteDataOptions } from '../interfaces/ps-remote-data-options';
|
|
2
2
|
import { PsRemoteDataResult } from '../interfaces/ps-remote-data-result';
|
|
3
|
-
export declare const usePsRemoteData: <T>({ key, load, refreshKey, }: PsRemoteDataOptions<T>) => PsRemoteDataResult<T>;
|
|
3
|
+
export declare const usePsRemoteData: <T>({ key, load, onWriteFailed, refreshKey, }: PsRemoteDataOptions<T>) => PsRemoteDataResult<T>;
|
|
@@ -1,18 +1,27 @@
|
|
|
1
|
-
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
-
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { usePsChangeQueue, usePsOptimisticRows } from '@pallasoft/ps-utilities/react';
|
|
3
|
+
import { PsRemoteDataService } from '../services/ps-remote-data-service';
|
|
4
|
+
export const usePsRemoteData = ({ key, load, onWriteFailed, refreshKey, }) => {
|
|
3
5
|
const [state, setState] = useState({
|
|
4
6
|
data: null,
|
|
5
7
|
error: false,
|
|
6
8
|
isLoading: true,
|
|
7
9
|
});
|
|
10
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
11
|
+
const isSavingRef = useRef(false);
|
|
12
|
+
const submittedRef = useRef(0);
|
|
8
13
|
const dataRef = useRef(null);
|
|
9
14
|
const loadRef = useRef(load);
|
|
15
|
+
const onWriteFailedRef = useRef(onWriteFailed);
|
|
10
16
|
const versionRef = useRef(0);
|
|
11
|
-
const queueRef = useRef(Promise.resolve(true));
|
|
12
|
-
const pendingRef = useRef(0);
|
|
13
17
|
useEffect(() => {
|
|
14
18
|
loadRef.current = load;
|
|
19
|
+
onWriteFailedRef.current = onWriteFailed;
|
|
15
20
|
});
|
|
21
|
+
const setSaving = useCallback((value) => {
|
|
22
|
+
isSavingRef.current = value;
|
|
23
|
+
setIsSaving(value);
|
|
24
|
+
}, []);
|
|
16
25
|
const fetch = useCallback(async () => {
|
|
17
26
|
versionRef.current += 1;
|
|
18
27
|
const version = versionRef.current;
|
|
@@ -31,42 +40,63 @@ export const usePsRemoteData = ({ key, load, refreshKey, }) => {
|
|
|
31
40
|
}, []);
|
|
32
41
|
useEffect(() => {
|
|
33
42
|
dataRef.current = null;
|
|
34
|
-
|
|
43
|
+
setSaving(false);
|
|
35
44
|
setState({ data: null, error: false, isLoading: true });
|
|
36
45
|
void fetch();
|
|
37
46
|
return () => {
|
|
38
47
|
versionRef.current += 1;
|
|
39
48
|
};
|
|
40
|
-
}, [key, fetch]);
|
|
49
|
+
}, [key, fetch, setSaving]);
|
|
41
50
|
const refreshRef = useRef(refreshKey);
|
|
42
51
|
useEffect(() => {
|
|
43
52
|
if (refreshRef.current === refreshKey)
|
|
44
53
|
return;
|
|
45
54
|
refreshRef.current = refreshKey;
|
|
46
|
-
if (
|
|
55
|
+
if (!isSavingRef.current)
|
|
47
56
|
void fetch();
|
|
48
57
|
}, [fetch, refreshKey]);
|
|
58
|
+
const apply = useCallback((rows, updates) => PsRemoteDataService.apply(rows, updates), []);
|
|
59
|
+
const rows = useMemo(() => PsRemoteDataService.rowsOf(state.data), [state.data]);
|
|
60
|
+
const optimistic = usePsOptimisticRows({ apply, isSaving, rows });
|
|
61
|
+
const rememberRef = useRef(optimistic.remember);
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
rememberRef.current = optimistic.remember;
|
|
64
|
+
});
|
|
65
|
+
const send = useCallback(async (writes) => {
|
|
66
|
+
for (const item of writes) {
|
|
67
|
+
await item.request();
|
|
68
|
+
item.resolve(true);
|
|
69
|
+
}
|
|
70
|
+
}, []);
|
|
71
|
+
const onFailed = useCallback((failed, dropped) => {
|
|
72
|
+
for (const item of [...failed, ...dropped]) {
|
|
73
|
+
optimistic.forgetLast();
|
|
74
|
+
item.resolve(false);
|
|
75
|
+
}
|
|
76
|
+
if (onWriteFailedRef.current !== undefined)
|
|
77
|
+
onWriteFailedRef.current();
|
|
78
|
+
}, [optimistic.forgetLast]);
|
|
79
|
+
const onDrained = useCallback(() => {
|
|
80
|
+
const through = submittedRef.current;
|
|
81
|
+
void fetch().then(() => {
|
|
82
|
+
if (submittedRef.current === through)
|
|
83
|
+
setSaving(false);
|
|
84
|
+
});
|
|
85
|
+
}, [fetch, setSaving]);
|
|
86
|
+
const queue = usePsChangeQueue({ onDrained, onFailed, send });
|
|
87
|
+
const { enqueue } = queue;
|
|
49
88
|
const write = useCallback((update, request) => {
|
|
50
|
-
|
|
51
|
-
if (previous === null)
|
|
89
|
+
if (dataRef.current === null)
|
|
52
90
|
return Promise.resolve(false);
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
setState({ data: previous, error: false, isLoading: false });
|
|
65
|
-
}
|
|
66
|
-
if (pendingRef.current === 0)
|
|
67
|
-
void fetch();
|
|
68
|
-
return isSaved;
|
|
69
|
-
});
|
|
70
|
-
}, [fetch]);
|
|
71
|
-
return { ...state, reload: fetch, write };
|
|
91
|
+
submittedRef.current += 1;
|
|
92
|
+
setSaving(true);
|
|
93
|
+
rememberRef.current(update);
|
|
94
|
+
return new Promise((resolve) => enqueue({ request, resolve }));
|
|
95
|
+
}, [enqueue, setSaving]);
|
|
96
|
+
return {
|
|
97
|
+
...state,
|
|
98
|
+
data: PsRemoteDataService.shown(optimistic.rows),
|
|
99
|
+
reload: fetch,
|
|
100
|
+
write,
|
|
101
|
+
};
|
|
72
102
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type PsRemoteDataUpdate<T> = (data: T) => T;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { PsRemoteDataUpdate } from '../interfaces/ps-remote-data-update';
|
|
2
|
+
export declare class PsRemoteDataService {
|
|
3
|
+
static rowsOf<T>(data: T | null): T[];
|
|
4
|
+
static apply<T>(rows: T[], updates: PsRemoteDataUpdate<T>[]): T[];
|
|
5
|
+
static shown<T>(rows: T[]): T | null;
|
|
6
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export class PsRemoteDataService {
|
|
2
|
+
static rowsOf(data) {
|
|
3
|
+
return data === null ? [] : [data];
|
|
4
|
+
}
|
|
5
|
+
static apply(rows, updates) {
|
|
6
|
+
if (rows.length === 0)
|
|
7
|
+
return rows;
|
|
8
|
+
return [updates.reduce((current, update) => update(current), rows[0])];
|
|
9
|
+
}
|
|
10
|
+
static shown(rows) {
|
|
11
|
+
return rows.length === 0 ? null : rows[0];
|
|
12
|
+
}
|
|
13
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pallasoft/ps-mobile-components",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Native iOS (SwiftUI) and Android (Jetpack Compose) Ps components for Pallasoft mobile apps",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@pallasoft/ps-design-system": "^1.17.0",
|
|
38
38
|
"@pallasoft/ps-icons": "^1.11.0",
|
|
39
|
-
"@pallasoft/ps-utilities": "^2.
|
|
39
|
+
"@pallasoft/ps-utilities": "^2.27.0"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
42
|
"@expo/ui": "~58.0.0",
|