react-resource-ui 0.1.2 → 0.1.3

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/index.cjs CHANGED
@@ -73,7 +73,8 @@ function useResource(config) {
73
73
  const getData = (0, import_react.useMemo)(() => normalizeSource(source), [source]);
74
74
  const prevScrollTop = (0, import_react.useRef)(0);
75
75
  const scrollRef = (0, import_react.useRef)(null);
76
- const hasMore = (0, import_react.useRef)(true);
76
+ const [hasNext, setHasNext] = (0, import_react.useState)(true);
77
+ const total = (0, import_react.useRef)(null);
77
78
  async function asyncNormalize(localPage) {
78
79
  const params = {
79
80
  page: localPage,
@@ -92,12 +93,14 @@ function useResource(config) {
92
93
  async function orchestrator() {
93
94
  const isPageMode = pagination?.type === "page";
94
95
  prevScrollTop.current = scrollTop;
95
- const cached = cache.current[page];
96
+ const localPage = page;
97
+ const cached = cache.current[localPage];
96
98
  if (isPageMode && cached) {
97
99
  setData(cached);
98
100
  return;
99
101
  }
100
- const localPage = page;
102
+ setHasNext(true);
103
+ total.current = null;
101
104
  requestTracker.current += 1;
102
105
  const currentRequestId = requestTracker.current;
103
106
  setLoading(true);
@@ -113,9 +116,21 @@ function useResource(config) {
113
116
  setError(result.error);
114
117
  return;
115
118
  }
116
- const rawData = result.data;
117
- if (!rawData || rawData.length < 1) return;
118
- if (rawData.length < pageSize) hasMore.current = false;
119
+ let rawData;
120
+ if (Array.isArray(result.data)) {
121
+ rawData = result.data;
122
+ } else {
123
+ total.current = result.data.total;
124
+ rawData = result.data.data;
125
+ }
126
+ if (total.current !== null) {
127
+ setHasNext(localPage < Math.ceil(total.current / pageSize));
128
+ } else {
129
+ if (rawData.length < pageSize) {
130
+ setHasNext(false);
131
+ }
132
+ }
133
+ if (!rawData) return;
119
134
  setData((prev) => {
120
135
  if (isPageMode) return rawData;
121
136
  const indexStart = (localPage - 1) * pageSize;
@@ -135,7 +150,7 @@ function useResource(config) {
135
150
  }
136
151
  }
137
152
  (0, import_react.useEffect)(() => {
138
- if (!hasMore.current) return;
153
+ if (!hasNext) return;
139
154
  if (pagination?.type !== "infinite") return;
140
155
  const el = scrollRef.current;
141
156
  if (!el) return;
@@ -145,7 +160,7 @@ function useResource(config) {
145
160
  setPage((prev) => prev + 1);
146
161
  })();
147
162
  }
148
- }, [scrollTop, pagination?.type, loading]);
163
+ }, [scrollTop, pagination?.type, loading, hasNext]);
149
164
  (0, import_react.useEffect)(() => {
150
165
  orchestrator();
151
166
  }, [page, pageSize, source, pagination?.type]);
@@ -168,39 +183,59 @@ function useResource(config) {
168
183
  let offsetY = 0;
169
184
  let totalHeight = 0;
170
185
  if (shouldVirtualize) {
171
- const total = data.length;
186
+ const total2 = data.length;
172
187
  const rawStart = Math.floor(scrollTop / itemHeight);
173
188
  const startIndex = Math.max(0, rawStart - 2);
174
189
  const visibleCount = Math.ceil(containerHeight / itemHeight);
175
- const endIndex = Math.min(startIndex + visibleCount + 4, total);
190
+ const endIndex = Math.min(startIndex + visibleCount + 4, total2);
176
191
  finalData = data.slice(startIndex, endIndex);
177
192
  offsetY = itemHeight * startIndex;
178
- totalHeight = total * itemHeight;
193
+ totalHeight = total2 * itemHeight;
179
194
  }
180
- return { data: finalData, loading, error, page, setPage, setScrollTop, offsetY, totalHeight, totalItems: data.length, scrollRef };
195
+ return { data: finalData, loading, error, page, setPage, setScrollTop, offsetY, totalHeight, totalItems: data.length, scrollRef, hasNext };
181
196
  }
182
197
 
183
198
  // src/components/DataTable.tsx
184
- var import_react2 = require("react");
185
199
  var import_jsx_runtime = require("react/jsx-runtime");
186
200
  function DataTable(props) {
187
- const { data, loading, error, page, setPage, setScrollTop, type, virtualization } = props;
188
- const isFetchingRef = (0, import_react2.useRef)(false);
189
- (0, import_react2.useEffect)(() => {
190
- isFetchingRef.current = false;
191
- }, [data]);
201
+ const {
202
+ data,
203
+ loading,
204
+ error,
205
+ page,
206
+ setPage,
207
+ setScrollTop,
208
+ type,
209
+ virtualization,
210
+ hasNext
211
+ } = props;
212
+ const offsetY = props.offsetY ?? 0;
213
+ const totalHeight = props.totalHeight ?? 0;
214
+ const itemHeight = 50;
215
+ const colCount = data[0] ? Object.keys(data[0]).length : 1;
192
216
  if (loading) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: "Loading..." });
193
217
  if (error) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: error.message });
194
- if ((props.totalItems ?? data.length) === 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "No Data to create Table" });
218
+ if ((props.totalItems ?? data.length) === 0)
219
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "No Data to create Table" });
195
220
  const content = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", { children: [
196
221
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tr", { children: Object.keys(data[0]).map((val) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: val }, val)) }) }),
197
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: data.map((val, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tr", { children: Object.keys(val).map((key) => {
198
- const value = val[key];
199
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: value != null ? value.toString() : "-" }, key);
200
- }) }, index)) })
222
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tbody", { children: [
223
+ virtualization && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tr", { style: { height: offsetY }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { colSpan: colCount }) }),
224
+ data.map((val, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tr", { children: Object.keys(val).map((key) => {
225
+ const value = val[key];
226
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "cell", children: value != null ? value.toString() : "-" }) }, key);
227
+ }) }, index)),
228
+ virtualization && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
229
+ "tr",
230
+ {
231
+ style: {
232
+ height: totalHeight - offsetY - data.length * itemHeight
233
+ },
234
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { colSpan: colCount })
235
+ }
236
+ )
237
+ ] })
201
238
  ] });
202
- const offsetY = props.offsetY ?? 0;
203
- const totalHeight = props.totalHeight ?? 0;
204
239
  const finalTable = virtualization ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
205
240
  "div",
206
241
  {
@@ -209,29 +244,33 @@ function DataTable(props) {
209
244
  onScroll: (e) => {
210
245
  setScrollTop(e.currentTarget.scrollTop);
211
246
  },
212
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { height: totalHeight, position: "relative" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
213
- "div",
214
- {
215
- style: {
216
- transform: `translateY(${offsetY}px)`,
217
- position: "absolute",
218
- top: 0,
219
- left: 0,
220
- right: 0
221
- },
222
- children: content
223
- }
224
- ) })
247
+ children: content
225
248
  }
226
249
  ) : content;
227
250
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
228
251
  type === "page" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
229
252
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: () => setPage((prev) => Math.max(prev - 1, 1)), children: "prev" }),
230
253
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: page }),
231
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: () => setPage((prev) => prev + 1), children: "next" })
254
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
255
+ "button",
256
+ {
257
+ onClick: () => setPage((prev) => {
258
+ if (hasNext) return prev + 1;
259
+ return prev;
260
+ }),
261
+ children: "next"
262
+ }
263
+ )
232
264
  ] }),
233
265
  finalTable,
234
- type === "loadmore" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: () => setPage((prev) => prev + 1), children: "load more" })
266
+ type === "loadmore" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
267
+ "button",
268
+ {
269
+ disabled: !hasNext,
270
+ onClick: () => setPage((prev) => prev + 1),
271
+ children: "load more"
272
+ }
273
+ )
235
274
  ] });
236
275
  }
237
276
  // Annotate the CommonJS export names for ESM import in node:
package/dist/index.d.cts CHANGED
@@ -5,7 +5,10 @@ type ResourceParams = {
5
5
  page?: number;
6
6
  pageSize?: number;
7
7
  };
8
- type Source<T> = T[] | string | ((params: ResourceParams) => Promise<T[]>);
8
+ type Source<T> = T[] | string | ((params: ResourceParams) => Promise<T[] | {
9
+ data: T[];
10
+ total: number;
11
+ }>);
9
12
 
10
13
  type PaginationConfig = {
11
14
  type: "page" | "loadmore" | "infinite";
@@ -32,6 +35,7 @@ declare function useResource<T>(config: Config<T>): {
32
35
  totalHeight: number;
33
36
  totalItems: number;
34
37
  scrollRef: react.RefObject<HTMLDivElement | null>;
38
+ hasNext: boolean;
35
39
  };
36
40
 
37
41
  type DataTableProps<T extends Record<string, unknown>> = {
@@ -47,6 +51,7 @@ type DataTableProps<T extends Record<string, unknown>> = {
47
51
  totalHeight?: number;
48
52
  totalItems?: number;
49
53
  scrollRef?: React.RefObject<HTMLDivElement> | null;
54
+ hasNext: boolean;
50
55
  };
51
56
  declare function DataTable<T extends Record<string, unknown>>(props: DataTableProps<T>): react_jsx_runtime.JSX.Element;
52
57
 
package/dist/index.d.ts CHANGED
@@ -5,7 +5,10 @@ type ResourceParams = {
5
5
  page?: number;
6
6
  pageSize?: number;
7
7
  };
8
- type Source<T> = T[] | string | ((params: ResourceParams) => Promise<T[]>);
8
+ type Source<T> = T[] | string | ((params: ResourceParams) => Promise<T[] | {
9
+ data: T[];
10
+ total: number;
11
+ }>);
9
12
 
10
13
  type PaginationConfig = {
11
14
  type: "page" | "loadmore" | "infinite";
@@ -32,6 +35,7 @@ declare function useResource<T>(config: Config<T>): {
32
35
  totalHeight: number;
33
36
  totalItems: number;
34
37
  scrollRef: react.RefObject<HTMLDivElement | null>;
38
+ hasNext: boolean;
35
39
  };
36
40
 
37
41
  type DataTableProps<T extends Record<string, unknown>> = {
@@ -47,6 +51,7 @@ type DataTableProps<T extends Record<string, unknown>> = {
47
51
  totalHeight?: number;
48
52
  totalItems?: number;
49
53
  scrollRef?: React.RefObject<HTMLDivElement> | null;
54
+ hasNext: boolean;
50
55
  };
51
56
  declare function DataTable<T extends Record<string, unknown>>(props: DataTableProps<T>): react_jsx_runtime.JSX.Element;
52
57
 
package/dist/index.js CHANGED
@@ -46,7 +46,8 @@ function useResource(config) {
46
46
  const getData = useMemo(() => normalizeSource(source), [source]);
47
47
  const prevScrollTop = useRef(0);
48
48
  const scrollRef = useRef(null);
49
- const hasMore = useRef(true);
49
+ const [hasNext, setHasNext] = useState(true);
50
+ const total = useRef(null);
50
51
  async function asyncNormalize(localPage) {
51
52
  const params = {
52
53
  page: localPage,
@@ -65,12 +66,14 @@ function useResource(config) {
65
66
  async function orchestrator() {
66
67
  const isPageMode = pagination?.type === "page";
67
68
  prevScrollTop.current = scrollTop;
68
- const cached = cache.current[page];
69
+ const localPage = page;
70
+ const cached = cache.current[localPage];
69
71
  if (isPageMode && cached) {
70
72
  setData(cached);
71
73
  return;
72
74
  }
73
- const localPage = page;
75
+ setHasNext(true);
76
+ total.current = null;
74
77
  requestTracker.current += 1;
75
78
  const currentRequestId = requestTracker.current;
76
79
  setLoading(true);
@@ -86,9 +89,21 @@ function useResource(config) {
86
89
  setError(result.error);
87
90
  return;
88
91
  }
89
- const rawData = result.data;
90
- if (!rawData || rawData.length < 1) return;
91
- if (rawData.length < pageSize) hasMore.current = false;
92
+ let rawData;
93
+ if (Array.isArray(result.data)) {
94
+ rawData = result.data;
95
+ } else {
96
+ total.current = result.data.total;
97
+ rawData = result.data.data;
98
+ }
99
+ if (total.current !== null) {
100
+ setHasNext(localPage < Math.ceil(total.current / pageSize));
101
+ } else {
102
+ if (rawData.length < pageSize) {
103
+ setHasNext(false);
104
+ }
105
+ }
106
+ if (!rawData) return;
92
107
  setData((prev) => {
93
108
  if (isPageMode) return rawData;
94
109
  const indexStart = (localPage - 1) * pageSize;
@@ -108,7 +123,7 @@ function useResource(config) {
108
123
  }
109
124
  }
110
125
  useEffect(() => {
111
- if (!hasMore.current) return;
126
+ if (!hasNext) return;
112
127
  if (pagination?.type !== "infinite") return;
113
128
  const el = scrollRef.current;
114
129
  if (!el) return;
@@ -118,7 +133,7 @@ function useResource(config) {
118
133
  setPage((prev) => prev + 1);
119
134
  })();
120
135
  }
121
- }, [scrollTop, pagination?.type, loading]);
136
+ }, [scrollTop, pagination?.type, loading, hasNext]);
122
137
  useEffect(() => {
123
138
  orchestrator();
124
139
  }, [page, pageSize, source, pagination?.type]);
@@ -141,39 +156,59 @@ function useResource(config) {
141
156
  let offsetY = 0;
142
157
  let totalHeight = 0;
143
158
  if (shouldVirtualize) {
144
- const total = data.length;
159
+ const total2 = data.length;
145
160
  const rawStart = Math.floor(scrollTop / itemHeight);
146
161
  const startIndex = Math.max(0, rawStart - 2);
147
162
  const visibleCount = Math.ceil(containerHeight / itemHeight);
148
- const endIndex = Math.min(startIndex + visibleCount + 4, total);
163
+ const endIndex = Math.min(startIndex + visibleCount + 4, total2);
149
164
  finalData = data.slice(startIndex, endIndex);
150
165
  offsetY = itemHeight * startIndex;
151
- totalHeight = total * itemHeight;
166
+ totalHeight = total2 * itemHeight;
152
167
  }
153
- return { data: finalData, loading, error, page, setPage, setScrollTop, offsetY, totalHeight, totalItems: data.length, scrollRef };
168
+ return { data: finalData, loading, error, page, setPage, setScrollTop, offsetY, totalHeight, totalItems: data.length, scrollRef, hasNext };
154
169
  }
155
170
 
156
171
  // src/components/DataTable.tsx
157
- import { useEffect as useEffect2, useRef as useRef2 } from "react";
158
172
  import { jsx, jsxs } from "react/jsx-runtime";
159
173
  function DataTable(props) {
160
- const { data, loading, error, page, setPage, setScrollTop, type, virtualization } = props;
161
- const isFetchingRef = useRef2(false);
162
- useEffect2(() => {
163
- isFetchingRef.current = false;
164
- }, [data]);
174
+ const {
175
+ data,
176
+ loading,
177
+ error,
178
+ page,
179
+ setPage,
180
+ setScrollTop,
181
+ type,
182
+ virtualization,
183
+ hasNext
184
+ } = props;
185
+ const offsetY = props.offsetY ?? 0;
186
+ const totalHeight = props.totalHeight ?? 0;
187
+ const itemHeight = 50;
188
+ const colCount = data[0] ? Object.keys(data[0]).length : 1;
165
189
  if (loading) return /* @__PURE__ */ jsx("div", { children: "Loading..." });
166
190
  if (error) return /* @__PURE__ */ jsx("div", { children: error.message });
167
- if ((props.totalItems ?? data.length) === 0) return /* @__PURE__ */ jsx("p", { children: "No Data to create Table" });
191
+ if ((props.totalItems ?? data.length) === 0)
192
+ return /* @__PURE__ */ jsx("p", { children: "No Data to create Table" });
168
193
  const content = /* @__PURE__ */ jsxs("table", { children: [
169
194
  /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: Object.keys(data[0]).map((val) => /* @__PURE__ */ jsx("th", { children: val }, val)) }) }),
170
- /* @__PURE__ */ jsx("tbody", { children: data.map((val, index) => /* @__PURE__ */ jsx("tr", { children: Object.keys(val).map((key) => {
171
- const value = val[key];
172
- return /* @__PURE__ */ jsx("td", { children: value != null ? value.toString() : "-" }, key);
173
- }) }, index)) })
195
+ /* @__PURE__ */ jsxs("tbody", { children: [
196
+ virtualization && /* @__PURE__ */ jsx("tr", { style: { height: offsetY }, children: /* @__PURE__ */ jsx("td", { colSpan: colCount }) }),
197
+ data.map((val, index) => /* @__PURE__ */ jsx("tr", { children: Object.keys(val).map((key) => {
198
+ const value = val[key];
199
+ return /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("div", { className: "cell", children: value != null ? value.toString() : "-" }) }, key);
200
+ }) }, index)),
201
+ virtualization && /* @__PURE__ */ jsx(
202
+ "tr",
203
+ {
204
+ style: {
205
+ height: totalHeight - offsetY - data.length * itemHeight
206
+ },
207
+ children: /* @__PURE__ */ jsx("td", { colSpan: colCount })
208
+ }
209
+ )
210
+ ] })
174
211
  ] });
175
- const offsetY = props.offsetY ?? 0;
176
- const totalHeight = props.totalHeight ?? 0;
177
212
  const finalTable = virtualization ? /* @__PURE__ */ jsx(
178
213
  "div",
179
214
  {
@@ -182,29 +217,33 @@ function DataTable(props) {
182
217
  onScroll: (e) => {
183
218
  setScrollTop(e.currentTarget.scrollTop);
184
219
  },
185
- children: /* @__PURE__ */ jsx("div", { style: { height: totalHeight, position: "relative" }, children: /* @__PURE__ */ jsx(
186
- "div",
187
- {
188
- style: {
189
- transform: `translateY(${offsetY}px)`,
190
- position: "absolute",
191
- top: 0,
192
- left: 0,
193
- right: 0
194
- },
195
- children: content
196
- }
197
- ) })
220
+ children: content
198
221
  }
199
222
  ) : content;
200
223
  return /* @__PURE__ */ jsxs("div", { children: [
201
224
  type === "page" && /* @__PURE__ */ jsxs("div", { children: [
202
225
  /* @__PURE__ */ jsx("button", { onClick: () => setPage((prev) => Math.max(prev - 1, 1)), children: "prev" }),
203
226
  /* @__PURE__ */ jsx("span", { children: page }),
204
- /* @__PURE__ */ jsx("button", { onClick: () => setPage((prev) => prev + 1), children: "next" })
227
+ /* @__PURE__ */ jsx(
228
+ "button",
229
+ {
230
+ onClick: () => setPage((prev) => {
231
+ if (hasNext) return prev + 1;
232
+ return prev;
233
+ }),
234
+ children: "next"
235
+ }
236
+ )
205
237
  ] }),
206
238
  finalTable,
207
- type === "loadmore" && /* @__PURE__ */ jsx("button", { onClick: () => setPage((prev) => prev + 1), children: "load more" })
239
+ type === "loadmore" && /* @__PURE__ */ jsx(
240
+ "button",
241
+ {
242
+ disabled: !hasNext,
243
+ onClick: () => setPage((prev) => prev + 1),
244
+ children: "load more"
245
+ }
246
+ )
208
247
  ] });
209
248
  }
210
249
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-resource-ui",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
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",