react-resource-ui 0.1.0 → 0.1.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/README.md +98 -61
- package/dist/index.cjs +36 -31
- package/dist/index.js +36 -31
- package/package.json +5 -6
package/README.md
CHANGED
|
@@ -1,73 +1,110 @@
|
|
|
1
|
-
# React
|
|
1
|
+
# React Resource UI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A data orchestration layer for React focused on pagination and list-based UI patterns.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
npm: https://www.npmjs.com/package/react-resource-ui
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
|
|
7
|
+
---
|
|
9
8
|
|
|
10
|
-
##
|
|
9
|
+
## Overview
|
|
11
10
|
|
|
12
|
-
|
|
11
|
+
React Resource UI provides a unified abstraction for handling data fetching, pagination, and virtualization in React applications.
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
It simplifies common UI patterns such as tables and lists by removing the need to rewrite logic when switching between pagination strategies.
|
|
15
14
|
|
|
16
|
-
|
|
15
|
+
---
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
export default defineConfig([
|
|
20
|
-
globalIgnores(['dist']),
|
|
21
|
-
{
|
|
22
|
-
files: ['**/*.{ts,tsx}'],
|
|
23
|
-
extends: [
|
|
24
|
-
// Other configs...
|
|
17
|
+
## Installation
|
|
25
18
|
|
|
26
|
-
|
|
27
|
-
tseslint.configs.recommendedTypeChecked,
|
|
28
|
-
// Alternatively, use this for stricter rules
|
|
29
|
-
tseslint.configs.strictTypeChecked,
|
|
30
|
-
// Optionally, add this for stylistic rules
|
|
31
|
-
tseslint.configs.stylisticTypeChecked,
|
|
19
|
+
npm install react-resource-ui
|
|
32
20
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Basic Usage
|
|
24
|
+
|
|
25
|
+
import { useResource } from "react-resource-ui";
|
|
26
|
+
|
|
27
|
+
function App() {
|
|
28
|
+
const { data, loading, error, page, setPage } = useResource({
|
|
29
|
+
source: async ({ page = 1, pageSize = 10 }) => {
|
|
30
|
+
const skip = (page - 1) * pageSize;
|
|
31
|
+
const res = await fetch(
|
|
32
|
+
`https://dummyjson.com/todos?limit=${pageSize}&skip=${skip}`
|
|
33
|
+
);
|
|
34
|
+
const json = await res.json();
|
|
35
|
+
return json.todos;
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
pagination: {
|
|
39
|
+
type: "page",
|
|
40
|
+
pageSize: 20,
|
|
41
41
|
},
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
```js
|
|
49
|
-
// eslint.config.js
|
|
50
|
-
import reactX from 'eslint-plugin-react-x'
|
|
51
|
-
import reactDom from 'eslint-plugin-react-dom'
|
|
52
|
-
|
|
53
|
-
export default defineConfig([
|
|
54
|
-
globalIgnores(['dist']),
|
|
55
|
-
{
|
|
56
|
-
files: ['**/*.{ts,tsx}'],
|
|
57
|
-
extends: [
|
|
58
|
-
// Other configs...
|
|
59
|
-
// Enable lint rules for React
|
|
60
|
-
reactX.configs['recommended-typescript'],
|
|
61
|
-
// Enable lint rules for React DOM
|
|
62
|
-
reactDom.configs.recommended,
|
|
63
|
-
],
|
|
64
|
-
languageOptions: {
|
|
65
|
-
parserOptions: {
|
|
66
|
-
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
67
|
-
tsconfigRootDir: import.meta.dirname,
|
|
68
|
-
},
|
|
69
|
-
// other options...
|
|
42
|
+
|
|
43
|
+
virtualization: {
|
|
44
|
+
enabled: true,
|
|
45
|
+
itemHeight: 40,
|
|
46
|
+
containerHeight: 400,
|
|
70
47
|
},
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Pagination Modes
|
|
56
|
+
|
|
57
|
+
The same logic can be reused across different pagination strategies by updating configuration only.
|
|
58
|
+
|
|
59
|
+
Page-based:
|
|
60
|
+
pagination: { type: "page" }
|
|
61
|
+
|
|
62
|
+
Load more:
|
|
63
|
+
pagination: { type: "loadmore" }
|
|
64
|
+
|
|
65
|
+
Infinite scroll:
|
|
66
|
+
pagination: { type: "infinite" }
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Features
|
|
71
|
+
|
|
72
|
+
- Unified API for multiple pagination strategies
|
|
73
|
+
- Built-in virtualization support
|
|
74
|
+
- Works with async functions, URLs, or static data sources
|
|
75
|
+
- Request deduplication using request tracking
|
|
76
|
+
- Lightweight page-based caching
|
|
77
|
+
- Designed for table and list UIs
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Design Goals
|
|
82
|
+
|
|
83
|
+
- Reduce UI-specific data handling complexity
|
|
84
|
+
- Avoid rewriting logic when changing pagination behavior
|
|
85
|
+
- Provide a higher-level abstraction over common data-fetching patterns
|
|
86
|
+
- Keep configuration simple and predictable
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Status
|
|
91
|
+
|
|
92
|
+
Version: 0.1.0
|
|
93
|
+
|
|
94
|
+
This is an early release focused on core functionality.
|
|
95
|
+
Additional testing and edge case handling are in progress.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Roadmap
|
|
100
|
+
|
|
101
|
+
- Sorting and filtering support
|
|
102
|
+
- Form integration
|
|
103
|
+
- Improved caching strategies
|
|
104
|
+
- Developer tooling and debug utilities
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Author
|
|
109
|
+
|
|
110
|
+
Kalyan Mantha
|
package/dist/index.cjs
CHANGED
|
@@ -74,56 +74,61 @@ function useResource(config) {
|
|
|
74
74
|
const prevScrollTop = (0, import_react.useRef)(0);
|
|
75
75
|
const scrollRef = (0, import_react.useRef)(null);
|
|
76
76
|
const hasMore = (0, import_react.useRef)(true);
|
|
77
|
-
async function asyncNormalize() {
|
|
77
|
+
async function asyncNormalize(localPage) {
|
|
78
78
|
const params = {
|
|
79
|
-
page,
|
|
79
|
+
page: localPage,
|
|
80
80
|
pageSize
|
|
81
81
|
};
|
|
82
|
-
setLoading(true);
|
|
83
|
-
requestTracker.current += 1;
|
|
84
|
-
const currentRequestId = requestTracker.current;
|
|
85
|
-
setError(null);
|
|
86
82
|
try {
|
|
87
83
|
const rawData = await getData(params);
|
|
88
|
-
|
|
89
|
-
setLoading(false);
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
;
|
|
93
|
-
setLoading(false);
|
|
94
|
-
return rawData;
|
|
84
|
+
return { data: rawData, error: null };
|
|
95
85
|
} catch (err) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
setError(new Error("unknown error"));
|
|
101
|
-
}
|
|
102
|
-
setLoading(false);
|
|
103
|
-
}
|
|
86
|
+
return {
|
|
87
|
+
data: null,
|
|
88
|
+
error: err instanceof Error ? err : new Error("unknown error")
|
|
89
|
+
};
|
|
104
90
|
}
|
|
105
91
|
}
|
|
106
92
|
async function orchestrator() {
|
|
107
93
|
const isPageMode = pagination?.type === "page";
|
|
108
94
|
prevScrollTop.current = scrollTop;
|
|
109
95
|
const cached = cache.current[page];
|
|
110
|
-
if (isPageMode) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
96
|
+
if (isPageMode && cached) {
|
|
97
|
+
setData(cached);
|
|
98
|
+
return;
|
|
115
99
|
}
|
|
116
|
-
const
|
|
117
|
-
|
|
100
|
+
const localPage = page;
|
|
101
|
+
requestTracker.current += 1;
|
|
102
|
+
const currentRequestId = requestTracker.current;
|
|
103
|
+
setLoading(true);
|
|
104
|
+
setError(null);
|
|
105
|
+
const result = await asyncNormalize(localPage);
|
|
106
|
+
if (currentRequestId !== requestTracker.current) {
|
|
107
|
+
setLoading(false);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
;
|
|
111
|
+
setLoading(false);
|
|
112
|
+
if (result.error) {
|
|
113
|
+
setError(result.error);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const rawData = result.data;
|
|
117
|
+
if (!rawData || rawData.length < 1) return;
|
|
118
118
|
if (rawData.length < pageSize) hasMore.current = false;
|
|
119
119
|
setData((prev) => {
|
|
120
120
|
if (isPageMode) return rawData;
|
|
121
|
-
|
|
121
|
+
const indexStart = (localPage - 1) * pageSize;
|
|
122
|
+
const newData = [...prev];
|
|
123
|
+
for (let i = 0; i < rawData.length; i++) {
|
|
124
|
+
newData[indexStart + i] = rawData[i];
|
|
125
|
+
}
|
|
126
|
+
return newData;
|
|
122
127
|
});
|
|
123
128
|
if (isPageMode) {
|
|
124
|
-
cache.current[
|
|
129
|
+
cache.current[localPage] = rawData;
|
|
125
130
|
Object.keys(cache.current).forEach((val) => {
|
|
126
|
-
if (Math.abs(
|
|
131
|
+
if (Math.abs(localPage - +val) > 1) {
|
|
127
132
|
delete cache.current[+val];
|
|
128
133
|
}
|
|
129
134
|
});
|
package/dist/index.js
CHANGED
|
@@ -47,56 +47,61 @@ function useResource(config) {
|
|
|
47
47
|
const prevScrollTop = useRef(0);
|
|
48
48
|
const scrollRef = useRef(null);
|
|
49
49
|
const hasMore = useRef(true);
|
|
50
|
-
async function asyncNormalize() {
|
|
50
|
+
async function asyncNormalize(localPage) {
|
|
51
51
|
const params = {
|
|
52
|
-
page,
|
|
52
|
+
page: localPage,
|
|
53
53
|
pageSize
|
|
54
54
|
};
|
|
55
|
-
setLoading(true);
|
|
56
|
-
requestTracker.current += 1;
|
|
57
|
-
const currentRequestId = requestTracker.current;
|
|
58
|
-
setError(null);
|
|
59
55
|
try {
|
|
60
56
|
const rawData = await getData(params);
|
|
61
|
-
|
|
62
|
-
setLoading(false);
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
;
|
|
66
|
-
setLoading(false);
|
|
67
|
-
return rawData;
|
|
57
|
+
return { data: rawData, error: null };
|
|
68
58
|
} catch (err) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
setError(new Error("unknown error"));
|
|
74
|
-
}
|
|
75
|
-
setLoading(false);
|
|
76
|
-
}
|
|
59
|
+
return {
|
|
60
|
+
data: null,
|
|
61
|
+
error: err instanceof Error ? err : new Error("unknown error")
|
|
62
|
+
};
|
|
77
63
|
}
|
|
78
64
|
}
|
|
79
65
|
async function orchestrator() {
|
|
80
66
|
const isPageMode = pagination?.type === "page";
|
|
81
67
|
prevScrollTop.current = scrollTop;
|
|
82
68
|
const cached = cache.current[page];
|
|
83
|
-
if (isPageMode) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
69
|
+
if (isPageMode && cached) {
|
|
70
|
+
setData(cached);
|
|
71
|
+
return;
|
|
88
72
|
}
|
|
89
|
-
const
|
|
90
|
-
|
|
73
|
+
const localPage = page;
|
|
74
|
+
requestTracker.current += 1;
|
|
75
|
+
const currentRequestId = requestTracker.current;
|
|
76
|
+
setLoading(true);
|
|
77
|
+
setError(null);
|
|
78
|
+
const result = await asyncNormalize(localPage);
|
|
79
|
+
if (currentRequestId !== requestTracker.current) {
|
|
80
|
+
setLoading(false);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
;
|
|
84
|
+
setLoading(false);
|
|
85
|
+
if (result.error) {
|
|
86
|
+
setError(result.error);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const rawData = result.data;
|
|
90
|
+
if (!rawData || rawData.length < 1) return;
|
|
91
91
|
if (rawData.length < pageSize) hasMore.current = false;
|
|
92
92
|
setData((prev) => {
|
|
93
93
|
if (isPageMode) return rawData;
|
|
94
|
-
|
|
94
|
+
const indexStart = (localPage - 1) * pageSize;
|
|
95
|
+
const newData = [...prev];
|
|
96
|
+
for (let i = 0; i < rawData.length; i++) {
|
|
97
|
+
newData[indexStart + i] = rawData[i];
|
|
98
|
+
}
|
|
99
|
+
return newData;
|
|
95
100
|
});
|
|
96
101
|
if (isPageMode) {
|
|
97
|
-
cache.current[
|
|
102
|
+
cache.current[localPage] = rawData;
|
|
98
103
|
Object.keys(cache.current).forEach((val) => {
|
|
99
|
-
if (Math.abs(
|
|
104
|
+
if (Math.abs(localPage - +val) > 1) {
|
|
100
105
|
delete cache.current[+val];
|
|
101
106
|
}
|
|
102
107
|
});
|
package/package.json
CHANGED
|
@@ -1,27 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-resource-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "A high-level data orchestration hook for React with pagination, caching, and virtualization",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
7
7
|
"module": "dist/index.js",
|
|
8
8
|
"types": "dist/index.d.ts",
|
|
9
|
-
"files": [
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
10
12
|
"license": "MIT",
|
|
11
|
-
|
|
12
13
|
"scripts": {
|
|
13
14
|
"build": "tsup src/index.ts --format esm,cjs --dts"
|
|
14
15
|
},
|
|
15
|
-
|
|
16
16
|
"peerDependencies": {
|
|
17
17
|
"react": "^18 || ^19",
|
|
18
18
|
"react-dom": "^18 || ^19"
|
|
19
19
|
},
|
|
20
|
-
|
|
21
20
|
"devDependencies": {
|
|
22
21
|
"@types/react": "^19.2.14",
|
|
23
22
|
"@types/react-dom": "^19.2.3",
|
|
24
23
|
"tsup": "^8.5.1",
|
|
25
24
|
"typescript": "~5.9.3"
|
|
26
25
|
}
|
|
27
|
-
}
|
|
26
|
+
}
|