Skip to content

Commit b344dd6

Browse files
expose stateHash prop to React views for pagination/sort control
1 parent 809a7ab commit b344dd6

4 files changed

Lines changed: 58 additions & 3 deletions

File tree

‎common.js‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,17 +99,22 @@ A react view can be tableless or table-based. A tableless react view could for e
9999
100100
When a react view is tabless, it gets this properties:
101101
\`\`\`import React from "react";
102-
export default function App({viewName, query}) {...}
102+
export default function App({viewName, query, stateHash}) {...}
103103
\`\`\`
104104
When a react view is table based, it gets this properties:
105105
\`\`\`import React from "react";
106-
export default function App({viewName, query, tableName, rows, state}) {...}
106+
export default function App({viewName, query, tableName, rows, state, stateHash}) {...}
107107
\`\`\`
108108
- viewName: the name of the view
109109
- query: the query parameters of the view
110110
- tableName: the name of the Saltcorn table
111111
- rows: the rows of the table, this is an array of objects, each object is a row of the table
112112
- state: the state of the view, this is an object with the state of the view
113+
- stateHash: a short hash string identifying this view's state context. Use it to set pagination and sort parameters
114+
via set_state_field, for example set_state_field(\`_\${stateHash}_page\`, 2) to go to page 2,
115+
set_state_field(\`_\${stateHash}_pagesize\`, 10) to set the page size,
116+
set_state_field(\`_\${stateHash}_sortby\`, "name") to sort by a field,
117+
set_state_field(\`_\${stateHash}_sortdesc\`, true) to sort descending.
113118
114119
A react-view has access to bootstrap 5 styles. react-bootstrap is not available please use the normal bootstrap classes.
115120

‎main-code/index.js‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,10 @@ const initMain = async () => {
6464
const user = JSON.parse(
6565
decodeURIComponent(rootElement.getAttribute("user")),
6666
);
67+
const stateHash = rootElement.getAttribute("state-hash");
6768
try {
6869
const remote = await loadRemote(`${viewName}/${viewName}`);
69-
const props = { tableName, viewName, state, query, rows, user };
70+
const props = { tableName, viewName, state, query, rows, user, stateHash };
7071
const root = ReactDOMClient.createRoot(rootElement);
7172
root.render(React.createElement(remote.default, props));
7273
} catch (e) {

‎react_view.js‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const {
66
stateFieldsToWhere,
77
readState,
88
} = require("@saltcorn/data/plugin-helper");
9+
const { hashState } = require("@saltcorn/data/utils");
910
const {
1011
buildSafeViewName,
1112
buildAndUpdateView,
@@ -30,10 +31,12 @@ export default function App({ viewName, query${
3031
const run = async (table_id, viewname, { timestamp }, state, extra) => {
3132
const req = extra.req;
3233
const query = req.query || {};
34+
const stateHash = hashState(state, viewname);
3335
const props = {
3436
"view-name": buildSafeViewName(viewname),
3537
query: encodeURIComponent(JSON.stringify(query)),
3638
user: encodeURIComponent(JSON.stringify(req.user || {})),
39+
"state-hash": stateHash,
3740
};
3841
if (table_id) {
3942
// with table

‎tests/view.test.js‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const { getState } = require("@saltcorn/data/db/state");
22
const View = require("@saltcorn/data/models/view");
33
const { mockReqRes } = require("@saltcorn/data/tests/mocks");
4+
const { hashState } = require("@saltcorn/data/utils");
45
const { afterAll, beforeAll, describe, it, expect } = require("@jest/globals");
56

67
getState().registerPlugin("base", require("@saltcorn/data/base-plugin"));
@@ -11,6 +12,11 @@ beforeAll(async () => {
1112
await getState().refresh(true);
1213
});
1314

15+
const extractAttr = (html, attr) => {
16+
const m = html.match(new RegExp(`${attr}="([^"]*)"`));
17+
return m ? m[1] : null;
18+
};
19+
1420
describe("react view run tests", () => {
1521
it("run tableless view", async () => {
1622
const view = View.findOne({ name: "default_react_view" });
@@ -43,3 +49,43 @@ describe("react view run tests", () => {
4349
expect(result).toContain('timestamp="');
4450
});
4551
});
52+
53+
describe("stateHash prop tests", () => {
54+
it("tableless view exposes state-hash as a 5-char hex string", async () => {
55+
const view = View.findOne({ name: "default_react_view" });
56+
const result = await view.run({}, mockReqRes);
57+
expect(result).toContain('state-hash="');
58+
const hash = extractAttr(result, "state-hash");
59+
expect(hash).toMatch(/^[0-9a-f]{5}$/);
60+
});
61+
62+
it("table-based view exposes state-hash as a 5-char hex string", async () => {
63+
const view = View.findOne({ name: "react_view_with_data" });
64+
const result = await view.run({}, mockReqRes);
65+
expect(result).toContain('state-hash="');
66+
const hash = extractAttr(result, "state-hash");
67+
expect(hash).toMatch(/^[0-9a-f]{5}$/);
68+
});
69+
70+
it("state-hash matches hashState utility output", async () => {
71+
const view = View.findOne({ name: "react_view_with_data" });
72+
const state = {};
73+
const result = await view.run(state, mockReqRes);
74+
const hash = extractAttr(result, "state-hash");
75+
expect(hash).toBe(hashState(state, "react_view_with_data"));
76+
});
77+
78+
it("state-hash is stable when only pagination params change", async () => {
79+
const view = View.findOne({ name: "react_view_with_data" });
80+
const baseState = {};
81+
const baseResult = await view.run(baseState, mockReqRes);
82+
const baseHash = extractAttr(baseResult, "state-hash");
83+
84+
const baseHashValue = hashState(baseState, "react_view_with_data");
85+
const pagedState = { [`_${baseHashValue}_page`]: "2" };
86+
const pagedResult = await view.run(pagedState, mockReqRes);
87+
const pagedHash = extractAttr(pagedResult, "state-hash");
88+
89+
expect(pagedHash).toBe(baseHash);
90+
});
91+
});

0 commit comments

Comments
 (0)