@zeno-lib/forms 0.9.0 → 0.9.1
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,14 +1,15 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { evaluate } from "@tanstack/react-form";
|
|
3
|
-
import { useCallback, useRef } from "react";
|
|
3
|
+
import { useCallback, useLayoutEffect, useRef } from "react";
|
|
4
4
|
//#region src/lib/use-rebased-default-values.ts
|
|
5
5
|
function useRebasedDefaultValues(callerDefaults) {
|
|
6
6
|
const liveForm = useRef(null);
|
|
7
7
|
const lastCallerDefaults = useRef(callerDefaults);
|
|
8
8
|
let defaultValues = callerDefaults;
|
|
9
|
-
if (evaluate(lastCallerDefaults.current, callerDefaults))
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
if (liveForm.current && evaluate(lastCallerDefaults.current, callerDefaults)) defaultValues = liveForm.current.options.defaultValues;
|
|
10
|
+
useLayoutEffect(() => {
|
|
11
|
+
lastCallerDefaults.current = callerDefaults;
|
|
12
|
+
}, [callerDefaults]);
|
|
12
13
|
const onMount = useCallback((props) => {
|
|
13
14
|
liveForm.current = props.formApi;
|
|
14
15
|
}, []);
|
package/package.json
CHANGED
package/src/form-dialog.test.tsx
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
within,
|
|
8
8
|
} from "@zeno-lib/test/testing-library"
|
|
9
9
|
import userEvent from "@zeno-lib/test/user-event"
|
|
10
|
-
import { type ReactNode, useState } from "react"
|
|
10
|
+
import { type ReactNode, StrictMode, useState } from "react"
|
|
11
11
|
import { afterEach, describe, expect, onTestFinished, test, vi } from "vitest"
|
|
12
12
|
import { z } from "zod"
|
|
13
13
|
import { useForm } from "./create-form"
|
|
@@ -178,6 +178,13 @@ describe("FormDialog", () => {
|
|
|
178
178
|
expect(name.value).toBe("Ada")
|
|
179
179
|
})
|
|
180
180
|
|
|
181
|
+
test("opens with per-session defaults under Strict Mode", async () => {
|
|
182
|
+
const user = userEvent.setup()
|
|
183
|
+
render(<Harness />, { wrapper: StrictMode })
|
|
184
|
+
const name = await openEdit(user)
|
|
185
|
+
expect(name.value).toBe("Ada")
|
|
186
|
+
})
|
|
187
|
+
|
|
181
188
|
test("an edit opened on a record with keys the schema lacks saves to a .strict() schema", async () => {
|
|
182
189
|
const user = userEvent.setup()
|
|
183
190
|
const strictSchema = schema.strict()
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import { type AnyFormApi, evaluate } from "@tanstack/react-form"
|
|
4
|
-
import { useCallback, useRef } from "react"
|
|
4
|
+
import { useCallback, useLayoutEffect, useRef } from "react"
|
|
5
5
|
|
|
6
6
|
// TanStack's `useForm` calls `formApi.update(options)` after every render. While
|
|
7
7
|
// the form is untouched, `update` re-applies `options.defaultValues` whenever it
|
|
@@ -24,14 +24,19 @@ function useRebasedDefaultValues<T>(callerDefaults: T): {
|
|
|
24
24
|
const lastCallerDefaults = useRef(callerDefaults)
|
|
25
25
|
|
|
26
26
|
let defaultValues = callerDefaults
|
|
27
|
-
if (
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
lastCallerDefaults.current = callerDefaults
|
|
27
|
+
if (
|
|
28
|
+
liveForm.current &&
|
|
29
|
+
evaluate(lastCallerDefaults.current, callerDefaults)
|
|
30
|
+
) {
|
|
31
|
+
defaultValues = liveForm.current.options.defaultValues as T
|
|
33
32
|
}
|
|
34
33
|
|
|
34
|
+
// Write on commit, not during render: under Strict Mode the second render
|
|
35
|
+
// would see the new defaults as unchanged and forward the old ones.
|
|
36
|
+
useLayoutEffect(() => {
|
|
37
|
+
lastCallerDefaults.current = callerDefaults
|
|
38
|
+
}, [callerDefaults])
|
|
39
|
+
|
|
35
40
|
const onMount = useCallback((props: { formApi: AnyFormApi }) => {
|
|
36
41
|
liveForm.current = props.formApi
|
|
37
42
|
}, [])
|
|
@@ -65,30 +65,35 @@ function titleInput() {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
describe("useForm — reset(values) rebases defaults", () => {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
68
|
+
for (const reactStrictMode of [false, true]) {
|
|
69
|
+
test(`reset after submit sticks across rerenders with fixed defaults (reactStrictMode: ${reactStrictMode})`, async () => {
|
|
70
|
+
const user = userEvent.setup()
|
|
71
|
+
let state: Captured | undefined
|
|
72
|
+
const capture = vi.fn((next: Captured) => {
|
|
73
|
+
state = next
|
|
74
|
+
})
|
|
75
|
+
const record = { title: "Draft" }
|
|
76
|
+
const { rerender } = render(
|
|
77
|
+
<EditTitle capture={capture} record={record} />,
|
|
78
|
+
{ reactStrictMode }
|
|
79
|
+
)
|
|
76
80
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
81
|
+
await user.clear(titleInput())
|
|
82
|
+
await user.type(titleInput(), "Saved title")
|
|
83
|
+
await user.click(screen.getByRole("button", { name: SAVE }))
|
|
80
84
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
await waitFor(() => expect(state?.isTouched).toBe(false))
|
|
86
|
+
for (const tick of [1, 2, 3]) {
|
|
87
|
+
rerender(<EditTitle capture={capture} record={record} tick={tick} />)
|
|
88
|
+
}
|
|
89
|
+
await act(() => Promise.resolve())
|
|
86
90
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
91
|
+
expect(titleInput().value).toBe("Saved title")
|
|
92
|
+
expect(state?.values).toEqual({ title: "Saved title" })
|
|
93
|
+
expect(state?.isDefaultValue).toBe(true)
|
|
94
|
+
expect(state?.isDirty).toBe(false)
|
|
95
|
+
})
|
|
96
|
+
}
|
|
92
97
|
|
|
93
98
|
test("new caller defaults apply while the form is untouched", async () => {
|
|
94
99
|
let state: Captured | undefined
|