@homebound/form-state 2.2.2 → 2.2.6
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 +2 -2
- package/dist/formState.js +7 -4
- package/dist/formState.test.js +44 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,6 +4,6 @@ Push release.
|
|
|
4
4
|
|
|
5
5
|
# Todo
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
- Add conditional readonly logic, like `{ type: "field", readOnlyIf: i => i.isInternal.value }`
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
- Add `omitFromValue` so we can have two fields, `book.author.id` / `book.author.name`, where `book.author.name` is used for showing the author name, but `book.author.id` is the only field that is submitted to the server on mutation (maybe pair this with `Ref` based mutations)
|
package/dist/formState.js
CHANGED
|
@@ -42,8 +42,9 @@ function useFormState(opts) {
|
|
|
42
42
|
// For this useMemo, we (almost) never re-run so that we can have a stable `form` identity across query refreshes.
|
|
43
43
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
44
44
|
[config, ...(isWrappingMobxProxy ? dep : [])]);
|
|
45
|
-
//
|
|
46
|
-
|
|
45
|
+
// We use useEffect so that any mutations to the proxies, which will call `setState`s on any observers to
|
|
46
|
+
// queue their components' render), don't happen during our render, per https://fb.me/setstate-in-render.
|
|
47
|
+
react_1.useEffect(() => {
|
|
47
48
|
// Ignore the 1st run b/c our 1st useMemo already initialized `form` with the current `init` value.
|
|
48
49
|
// Also for mobx proxies, we recreate a new form-state every time init changes, so that our
|
|
49
50
|
// fields fundamentally pointing to the right proxy. So just defer to the ^ useMemo.
|
|
@@ -489,7 +490,7 @@ function newListFieldState(parentInstance, parentState, key, rules, listConfig,
|
|
|
489
490
|
}
|
|
490
491
|
}
|
|
491
492
|
// If we didn't have an existing child, just make a new object state
|
|
492
|
-
childState = createObjectState(config, value);
|
|
493
|
+
childState = createObjectState(config, value, { onBlur });
|
|
493
494
|
rowMap.set(value, childState);
|
|
494
495
|
}
|
|
495
496
|
// Return the already-observable'd value so that our `parent.value[key] = values` doesn't re-proxy things
|
|
@@ -499,11 +500,12 @@ function newListFieldState(parentInstance, parentState, key, rules, listConfig,
|
|
|
499
500
|
},
|
|
500
501
|
add(value, spliceIndex) {
|
|
501
502
|
// This is called by the user, so value should be a non-proxy value we should keep
|
|
502
|
-
const childState = createObjectState(config, value);
|
|
503
|
+
const childState = createObjectState(config, value, { onBlur });
|
|
503
504
|
rowMap.set(value, childState);
|
|
504
505
|
this.ensureSet();
|
|
505
506
|
this.value.splice(typeof spliceIndex === "number" ? spliceIndex : this.value.length, 0, childState.value);
|
|
506
507
|
_tick.value++;
|
|
508
|
+
onBlur();
|
|
507
509
|
},
|
|
508
510
|
remove(indexOrValue) {
|
|
509
511
|
this.ensureSet();
|
|
@@ -517,6 +519,7 @@ function newListFieldState(parentInstance, parentState, key, rules, listConfig,
|
|
|
517
519
|
}
|
|
518
520
|
}
|
|
519
521
|
_tick.value++;
|
|
522
|
+
onBlur();
|
|
520
523
|
},
|
|
521
524
|
reset() {
|
|
522
525
|
if (originalCopy) {
|
package/dist/formState.test.js
CHANGED
|
@@ -321,6 +321,16 @@ describe("formState", () => {
|
|
|
321
321
|
a1.books.remove(0);
|
|
322
322
|
expect(a1.dirty).toBeFalsy();
|
|
323
323
|
});
|
|
324
|
+
it("it calls onBlur when adding or removing to a list", () => {
|
|
325
|
+
const onBlur = jest.fn();
|
|
326
|
+
const a1 = createAuthorInputState({ books: [] }, onBlur);
|
|
327
|
+
expect(a1.books.dirty).toBeFalsy();
|
|
328
|
+
a1.books.add({ title: "t2" });
|
|
329
|
+
expect(a1.books.dirty).toBeTruthy();
|
|
330
|
+
a1.books.remove(0);
|
|
331
|
+
expect(a1.dirty).toBeFalsy();
|
|
332
|
+
expect(onBlur).toBeCalledTimes(2);
|
|
333
|
+
});
|
|
324
334
|
it("knows list of primitives are dirty", () => {
|
|
325
335
|
const a1 = formState_1.createObjectState({ favoriteColors: { type: "value" } }, {});
|
|
326
336
|
expect(a1.favoriteColors.dirty).toBeFalsy();
|
|
@@ -1322,6 +1332,38 @@ describe("formState", () => {
|
|
|
1322
1332
|
expect(r.firstName().textContent).toEqual("f2");
|
|
1323
1333
|
expect(r.fullName().textContent).toEqual("f2 l2");
|
|
1324
1334
|
});
|
|
1335
|
+
it("can trigger onBlur for fields in list that were initially undefined", async () => {
|
|
1336
|
+
const onBlur = jest.fn();
|
|
1337
|
+
// Given a component
|
|
1338
|
+
function TestComponent() {
|
|
1339
|
+
// When the data is initially undefined
|
|
1340
|
+
const [data, setData] = react_1.useState();
|
|
1341
|
+
const data2 = { books: [{ title: "Title 1" }] };
|
|
1342
|
+
const form = formState_1.useFormState({
|
|
1343
|
+
config: authorWithBooksConfig,
|
|
1344
|
+
init: { input: data, map: (d) => d, ifUndefined: { books: [] } },
|
|
1345
|
+
autoSave: () => onBlur(),
|
|
1346
|
+
});
|
|
1347
|
+
return (jsx_runtime_1.jsx(mobx_react_1.Observer, { children: () => (jsx_runtime_1.jsxs("div", { children: [jsx_runtime_1.jsx("button", { "data-testid": "refreshData", onClick: () => setData(data2) }, void 0),
|
|
1348
|
+
jsx_runtime_1.jsx("button", { "data-testid": "add", onClick: () => form.books.add({ title: "New Book" }) }, void 0),
|
|
1349
|
+
jsx_runtime_1.jsx("button", { "data-testid": "blur", onClick: () => form.books.rows[0].title.blur() }, void 0),
|
|
1350
|
+
jsx_runtime_1.jsx("button", { "data-testid": "blurNew", onClick: () => form.books.rows[1].title.blur() }, void 0)] }, void 0)) }, void 0));
|
|
1351
|
+
}
|
|
1352
|
+
// Given a formState with `onBlur` set
|
|
1353
|
+
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, {}, void 0));
|
|
1354
|
+
// When the data/child is now available
|
|
1355
|
+
rtl_utils_1.click(r.refreshData);
|
|
1356
|
+
// And the field is blurred
|
|
1357
|
+
rtl_utils_1.click(r.blur);
|
|
1358
|
+
// Then expect onBlur to triggered
|
|
1359
|
+
expect(onBlur).toBeCalledTimes(1);
|
|
1360
|
+
// And when adding a new book
|
|
1361
|
+
rtl_utils_1.click(r.add);
|
|
1362
|
+
// And the new book is blurred
|
|
1363
|
+
rtl_utils_1.click(r.blurNew);
|
|
1364
|
+
// Then expect onBlur to be triggered again
|
|
1365
|
+
expect(onBlur).toBeCalledTimes(3);
|
|
1366
|
+
});
|
|
1325
1367
|
});
|
|
1326
1368
|
class ObservableObject {
|
|
1327
1369
|
constructor() {
|
|
@@ -1354,8 +1396,8 @@ const authorWithBooksConfig = {
|
|
|
1354
1396
|
},
|
|
1355
1397
|
},
|
|
1356
1398
|
};
|
|
1357
|
-
function createAuthorInputState(input) {
|
|
1358
|
-
return formState_1.createObjectState(authorWithBooksConfig, input);
|
|
1399
|
+
function createAuthorInputState(input, onBlur) {
|
|
1400
|
+
return formState_1.createObjectState(authorWithBooksConfig, input, { onBlur });
|
|
1359
1401
|
}
|
|
1360
1402
|
const authorWithAddressConfig = {
|
|
1361
1403
|
id: { type: "value" },
|