diff --git a/src/useFormState.test.tsx b/src/useFormState.test.tsx index 94f00dd..e75074d 100644 --- a/src/useFormState.test.tsx +++ b/src/useFormState.test.tsx @@ -1,8 +1,8 @@ import { click, clickAndWait, render, typeAndWait, wait } from "@homebound/rtl-utils"; import { act } from "@testing-library/react"; import { makeAutoObservable, reaction } from "mobx"; -import { Observer } from "mobx-react"; -import { useMemo, useState } from "react"; +import { observer, Observer } from "mobx-react"; +import { useMemo, useRef, useState } from "react"; import { TextField } from "src/FormStateApp"; import { ObjectConfig } from "src/config"; import { ObjectState } from "src/fields/objectField"; @@ -117,6 +117,31 @@ describe("useFormState", () => { expect(r.baseElement.textContent).toEqual(""); }); + it("uses init.onlyOnce to not react to identity changes", async () => { + // Given a component + type FormValue = Pick; + const config: ObjectConfig = { firstName: { type: "value" } }; + const TestComponent = observer(() => { + // And we have an `init` value that is dynamic, so we can observe whether init reruns + const renderCount = useRef(0).current++; + const form = useFormState({ + config, + init: { input: { firstName: `${renderCount}` }, onlyOnce: true }, + }); + const [, setTick] = useState(0); + return ( +
+
+ ); + }); + const r = await render(); + expect(r.firstName).toHaveTextContent("0"); + click(r.change); + expect(r.firstName).toHaveTextContent("0"); + }); + it("keeps local changed values when a query refreshes", async () => { // Given a component function TestComponent() { diff --git a/src/useFormState.ts b/src/useFormState.ts index ab3fc1f..ccc1700 100644 --- a/src/useFormState.ts +++ b/src/useFormState.ts @@ -11,6 +11,7 @@ export type InputAndMap = { input: I; map?: (input: Exclude) => T; ifUndefined?: T; + onlyOnce?: boolean; }; export type QueryAndMap = { @@ -98,7 +99,13 @@ export function useFormState(opts: UseFormStateOpts): ObjectState const [firstInitValue] = useState(() => initValue(config, init)); const isWrappingMobxProxy = !isPlainObject(firstInitValue); // If they're using init.input, useMemo on it (and it might be an array), otherwise allow the identity of init be unstable - const dep = isInput(init) ? makeArray(init.input) : isQuery(init) ? [init.query.data, init.query.loading] : []; + const dep = isInput(init) + ? init.onlyOnce + ? [] + : makeArray(init.input) + : isQuery(init) + ? [init.query.data, init.query.loading] + : []; const form = useMemo( () => {