@homebound/form-state 2.8.0 → 2.9.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.
@@ -1,2 +1,6 @@
1
1
  /// <reference types="react" />
2
+ import { FieldState } from "./index";
2
3
  export declare function FormStateApp(): JSX.Element;
4
+ export declare function TextField(props: {
5
+ field: FieldState<any, string | null | undefined>;
6
+ }): JSX.Element;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.FormStateApp = void 0;
3
+ exports.TextField = exports.FormStateApp = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const mobx_react_1 = require("mobx-react");
6
6
  const index_1 = require("./index");
@@ -91,3 +91,4 @@ function TextField(props) {
91
91
  jsx_runtime_1.jsx("td", Object.assign({ "data-testid": `${field.key}_errors` }, { children: field.errors }), void 0),
92
92
  jsx_runtime_1.jsx("td", Object.assign({ "data-testid": `${field.key}_original` }, { children: field.originalValue }), void 0)] }, void 0) }, void 0)] }), void 0)] }, void 0)) }, void 0));
93
93
  }
94
+ exports.TextField = TextField;
@@ -12,7 +12,6 @@ let isAutoSaving = false;
12
12
  // `pendingAutoSave` is a flag for determining if we need to immediately call `maybeAutoSave` again after the initial Promise finishes
13
13
  // This could happen if a field triggers auto-save while another field's auto-save is already in progress.
14
14
  let pendingAutoSave = false;
15
- let timerId;
16
15
  /**
17
16
  * Creates a formState instance for editing in a form.
18
17
  */
@@ -39,7 +38,7 @@ function useFormState(opts) {
39
38
  if (autoSaveRef.current && form.dirty && form.valid && !isAutoSaving) {
40
39
  isAutoSaving = "queued";
41
40
  // We use setTimeout as a cheap way to wait until the end of the current event listener
42
- timerId = setTimeout(async () => {
41
+ setTimeout(async () => {
43
42
  try {
44
43
  // We technically don't flip to in-flight until after the call in case the
45
44
  // user's autoSave function itself wants to call a .set.
@@ -3,8 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const jsx_runtime_1 = require("react/jsx-runtime");
4
4
  const rtl_utils_1 = require("@homebound/rtl-utils");
5
5
  const react_1 = require("@testing-library/react");
6
+ const mobx_1 = require("mobx");
6
7
  const mobx_react_1 = require("mobx-react");
7
8
  const react_2 = require("react");
9
+ const FormStateApp_1 = require("./FormStateApp");
8
10
  const formState_1 = require("./formState");
9
11
  const useFormState_1 = require("./useFormState");
10
12
  describe("useFormState", () => {
@@ -425,6 +427,33 @@ describe("useFormState", () => {
425
427
  expect(autoSaveStub).toBeCalledWith({ id: "a:1", firstName: "Foo" });
426
428
  expect(autoSaveStub).toBeCalledWith({ id: "a:1", lastName: "Bar" });
427
429
  });
430
+ it("calls autoSave with results of calculations in addRules", async () => {
431
+ const autoSaveStub = jest.fn();
432
+ const config = {
433
+ id: { type: "value" },
434
+ firstName: { type: "value" },
435
+ lastName: { type: "value" },
436
+ };
437
+ // Given a component that is using autoSave
438
+ function TestComponent() {
439
+ const fs = useFormState_1.useFormState({
440
+ config,
441
+ addRules(fs) {
442
+ // And also has calculated values
443
+ mobx_1.reaction(() => fs.firstName.value, (curr) => (fs.lastName.value = curr));
444
+ },
445
+ autoSave: (fs) => autoSaveStub(fs.changedValue),
446
+ init: { id: "a:1" },
447
+ });
448
+ return jsx_runtime_1.jsx(FormStateApp_1.TextField, { field: fs.firstName }, void 0);
449
+ }
450
+ const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, {}, void 0));
451
+ // When the user sets one field
452
+ await rtl_utils_1.typeAndWait(r.firstName, "first");
453
+ // Then we only called autoSave once
454
+ expect(autoSaveStub).toBeCalledTimes(1);
455
+ expect(autoSaveStub).toBeCalledWith({ id: "a:1", firstName: "first", lastName: "first" });
456
+ });
428
457
  });
429
458
  const authorConfig = {
430
459
  id: { type: "value" },
@@ -35,7 +35,7 @@ declare type UseFormStatesOpts<T, I> = {
35
35
  */
36
36
  map?: (input: Exclude<I, null | undefined>) => T;
37
37
  };
38
- export declare function useFormStates<T, I>(opts: UseFormStatesOpts<T, I>): {
38
+ export declare function useFormStates<T, I = T>(opts: UseFormStatesOpts<T, I>): {
39
39
  getFormState: (input: I) => ObjectState<T>;
40
40
  };
41
41
  export {};
@@ -23,6 +23,13 @@ function useFormStates(opts) {
23
23
  if (autoSaveRef.current && form.dirty && form.valid && !isAutoSaving) {
24
24
  try {
25
25
  isAutoSaving = true;
26
+ // See if we have any reactions that want to run (i.e. added by addRules hooks)
27
+ await new Promise((resolve) => setTimeout(resolve, 0));
28
+ // If a reaction re-queued our form during the ^ wait, remove it
29
+ let i = -1;
30
+ while ((i = pendingAutoSaves.current.indexOf(form)) !== -1) {
31
+ pendingAutoSaves.current.splice(i, 1);
32
+ }
26
33
  await autoSaveRef.current(form);
27
34
  }
28
35
  finally {
@@ -2,8 +2,10 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const jsx_runtime_1 = require("react/jsx-runtime");
4
4
  const rtl_utils_1 = require("@homebound/rtl-utils");
5
+ const mobx_1 = require("mobx");
5
6
  const react_1 = require("react");
6
7
  const formState_1 = require("./formState");
8
+ const FormStateApp_1 = require("./FormStateApp");
7
9
  const useFormStates_1 = require("./useFormStates");
8
10
  describe("useFormStates", () => {
9
11
  it("can lazily create form states", async () => {
@@ -99,10 +101,9 @@ describe("useFormStates", () => {
99
101
  rtl_utils_1.click(r.focusSetAndSaveField2());
100
102
  // Awaits the promises for all methods triggered above
101
103
  await rtl_utils_1.wait();
102
- // Then expect the auto save to only have been called three times. Once with each set of changedValues.
103
- expect(autoSaveStub).toBeCalledTimes(3);
104
- expect(autoSaveStub).toBeCalledWith({ id: "a:1", firstName: "Foo" });
105
- expect(autoSaveStub).toBeCalledWith({ id: "a:1", lastName: "Bar" });
104
+ // Then expect the auto save to only have been called two times. Once with each set of changedValues.
105
+ expect(autoSaveStub).toBeCalledTimes(2);
106
+ expect(autoSaveStub).toBeCalledWith({ id: "a:1", firstName: "Foo", lastName: "Bar" });
106
107
  expect(autoSaveStub).toBeCalledWith({ id: "a:2", lastName: "Bar" });
107
108
  });
108
109
  it("clears out cache if configuration changes", async () => {
@@ -151,6 +152,39 @@ describe("useFormStates", () => {
151
152
  // Then addRules was only called once
152
153
  expect(addRules).toHaveBeenCalledTimes(1);
153
154
  });
155
+ it("calls autoSave with results of calculations in addRules", async () => {
156
+ // Given a user wants to use auto save
157
+ const autoSave = jest.fn();
158
+ function TestComponent() {
159
+ const config = {
160
+ id: { type: "value" },
161
+ firstName: { type: "value" },
162
+ lastName: { type: "value" },
163
+ };
164
+ const { getFormState } = useFormStates_1.useFormStates({
165
+ config,
166
+ getId: (o) => o.id,
167
+ addRules(fs) {
168
+ // And they have a reactive true that calculates last name
169
+ mobx_1.reaction(() => fs.firstName.value, (curr) => {
170
+ console.log({ curr });
171
+ fs.lastName.set(curr);
172
+ });
173
+ },
174
+ async autoSave(fs) {
175
+ autoSave(fs.changedValue);
176
+ },
177
+ });
178
+ return jsx_runtime_1.jsx(FormStateApp_1.TextField, { field: getFormState({ id: "a:1", firstName: "Brandon" }).firstName }, void 0);
179
+ }
180
+ // When we render
181
+ const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, {}, void 0));
182
+ // And update the firstName
183
+ await rtl_utils_1.typeAndWait(r.firstName(), "first");
184
+ // Then autoSave was called once with both input+calc'd values
185
+ expect(autoSave).toHaveBeenCalledTimes(1);
186
+ expect(autoSave).toHaveBeenCalledWith({ id: "a:1", firstName: "first", lastName: "first" });
187
+ });
154
188
  });
155
189
  function ChildComponent({ os }) {
156
190
  return jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "firstName" }, { children: os.firstName.value }), void 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homebound/form-state",
3
- "version": "2.8.0",
3
+ "version": "2.9.0",
4
4
  "author": "Homebound",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",