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 CHANGED
@@ -1,73 +1,110 @@
1
- # React + TypeScript + Vite
1
+ # React Resource UI
2
2
 
3
- This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
3
+ A data orchestration layer for React focused on pagination and list-based UI patterns.
4
4
 
5
- Currently, two official plugins are available:
5
+ npm: https://www.npmjs.com/package/react-resource-ui
6
6
 
7
- - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
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
- ## React Compiler
9
+ ## Overview
11
10
 
12
- The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
11
+ React Resource UI provides a unified abstraction for handling data fetching, pagination, and virtualization in React applications.
13
12
 
14
- ## Expanding the ESLint configuration
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
- If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
15
+ ---
17
16
 
18
- ```js
19
- export default defineConfig([
20
- globalIgnores(['dist']),
21
- {
22
- files: ['**/*.{ts,tsx}'],
23
- extends: [
24
- // Other configs...
17
+ ## Installation
25
18
 
26
- // Remove tseslint.configs.recommended and replace with this
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
- // Other configs...
34
- ],
35
- languageOptions: {
36
- parserOptions: {
37
- project: ['./tsconfig.node.json', './tsconfig.app.json'],
38
- tsconfigRootDir: import.meta.dirname,
39
- },
40
- // other options...
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
- You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
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
- if (currentRequestId !== requestTracker.current) {
89
- setLoading(false);
90
- return;
91
- }
92
- ;
93
- setLoading(false);
94
- return rawData;
84
+ return { data: rawData, error: null };
95
85
  } catch (err) {
96
- if (currentRequestId === requestTracker.current) {
97
- if (err instanceof Error) {
98
- setError(err);
99
- } else {
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
- if (cached) {
112
- setData(cached);
113
- return;
114
- }
96
+ if (isPageMode && cached) {
97
+ setData(cached);
98
+ return;
115
99
  }
116
- const rawData = await asyncNormalize();
117
- if (!rawData) return;
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
- return page === 1 ? rawData : [...prev, ...rawData];
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[page] = rawData;
129
+ cache.current[localPage] = rawData;
125
130
  Object.keys(cache.current).forEach((val) => {
126
- if (Math.abs(page - +val) > 1) {
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
- if (currentRequestId !== requestTracker.current) {
62
- setLoading(false);
63
- return;
64
- }
65
- ;
66
- setLoading(false);
67
- return rawData;
57
+ return { data: rawData, error: null };
68
58
  } catch (err) {
69
- if (currentRequestId === requestTracker.current) {
70
- if (err instanceof Error) {
71
- setError(err);
72
- } else {
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
- if (cached) {
85
- setData(cached);
86
- return;
87
- }
69
+ if (isPageMode && cached) {
70
+ setData(cached);
71
+ return;
88
72
  }
89
- const rawData = await asyncNormalize();
90
- if (!rawData) return;
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
- return page === 1 ? rawData : [...prev, ...rawData];
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[page] = rawData;
102
+ cache.current[localPage] = rawData;
98
103
  Object.keys(cache.current).forEach((val) => {
99
- if (Math.abs(page - +val) > 1) {
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.0",
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": ["dist"],
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
+ }