react-resource-ui 0.1.2 → 0.1.4-dev.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/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,62 @@ 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 columns = data[0] ? Object.keys(data[0]) : [];
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
- /* @__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)) })
221
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tr", { children: columns.map((val) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: val }, val)) }) }),
222
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tbody", { children: [
223
+ virtualization && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tr", { style: { height: offsetY }, children: columns.map((key) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {}, key)) }),
224
+ data.map((val, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tr", { children: columns.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: Math.max(
233
+ 0,
234
+ totalHeight - offsetY - data.length * itemHeight
235
+ )
236
+ },
237
+ children: columns.map((key) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {}, key))
238
+ }
239
+ )
240
+ ] })
201
241
  ] });
202
- const offsetY = props.offsetY ?? 0;
203
- const totalHeight = props.totalHeight ?? 0;
204
242
  const finalTable = virtualization ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
205
243
  "div",
206
244
  {
@@ -209,29 +247,33 @@ function DataTable(props) {
209
247
  onScroll: (e) => {
210
248
  setScrollTop(e.currentTarget.scrollTop);
211
249
  },
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
- ) })
250
+ children: content
225
251
  }
226
252
  ) : content;
227
253
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
228
254
  type === "page" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
229
255
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: () => setPage((prev) => Math.max(prev - 1, 1)), children: "prev" }),
230
256
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: page }),
231
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: () => setPage((prev) => prev + 1), children: "next" })
257
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
258
+ "button",
259
+ {
260
+ onClick: () => setPage((prev) => {
261
+ if (hasNext) return prev + 1;
262
+ return prev;
263
+ }),
264
+ children: "next"
265
+ }
266
+ )
232
267
  ] }),
233
268
  finalTable,
234
- type === "loadmore" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: () => setPage((prev) => prev + 1), children: "load more" })
269
+ type === "loadmore" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
270
+ "button",
271
+ {
272
+ disabled: !hasNext,
273
+ onClick: () => setPage((prev) => prev + 1),
274
+ children: "load more"
275
+ }
276
+ )
235
277
  ] });
236
278
  }
237
279
  // 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,62 @@ 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 columns = data[0] ? Object.keys(data[0]) : [];
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
- /* @__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)) })
194
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: columns.map((val) => /* @__PURE__ */ jsx("th", { children: val }, val)) }) }),
195
+ /* @__PURE__ */ jsxs("tbody", { children: [
196
+ virtualization && /* @__PURE__ */ jsx("tr", { style: { height: offsetY }, children: columns.map((key) => /* @__PURE__ */ jsx("td", {}, key)) }),
197
+ data.map((val, index) => /* @__PURE__ */ jsx("tr", { children: columns.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: Math.max(
206
+ 0,
207
+ totalHeight - offsetY - data.length * itemHeight
208
+ )
209
+ },
210
+ children: columns.map((key) => /* @__PURE__ */ jsx("td", {}, key))
211
+ }
212
+ )
213
+ ] })
174
214
  ] });
175
- const offsetY = props.offsetY ?? 0;
176
- const totalHeight = props.totalHeight ?? 0;
177
215
  const finalTable = virtualization ? /* @__PURE__ */ jsx(
178
216
  "div",
179
217
  {
@@ -182,29 +220,33 @@ function DataTable(props) {
182
220
  onScroll: (e) => {
183
221
  setScrollTop(e.currentTarget.scrollTop);
184
222
  },
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
- ) })
223
+ children: content
198
224
  }
199
225
  ) : content;
200
226
  return /* @__PURE__ */ jsxs("div", { children: [
201
227
  type === "page" && /* @__PURE__ */ jsxs("div", { children: [
202
228
  /* @__PURE__ */ jsx("button", { onClick: () => setPage((prev) => Math.max(prev - 1, 1)), children: "prev" }),
203
229
  /* @__PURE__ */ jsx("span", { children: page }),
204
- /* @__PURE__ */ jsx("button", { onClick: () => setPage((prev) => prev + 1), children: "next" })
230
+ /* @__PURE__ */ jsx(
231
+ "button",
232
+ {
233
+ onClick: () => setPage((prev) => {
234
+ if (hasNext) return prev + 1;
235
+ return prev;
236
+ }),
237
+ children: "next"
238
+ }
239
+ )
205
240
  ] }),
206
241
  finalTable,
207
- type === "loadmore" && /* @__PURE__ */ jsx("button", { onClick: () => setPage((prev) => prev + 1), children: "load more" })
242
+ type === "loadmore" && /* @__PURE__ */ jsx(
243
+ "button",
244
+ {
245
+ disabled: !hasNext,
246
+ onClick: () => setPage((prev) => prev + 1),
247
+ children: "load more"
248
+ }
249
+ )
208
250
  ] });
209
251
  }
210
252
  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.4-dev.0",
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",