@mohasinac/appkit 4.63.2 → 4.63.3
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/dist/.build-stamp.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"stampVersion": 1,
|
|
3
|
-
"version": "4.63.
|
|
4
|
-
"head": "
|
|
3
|
+
"version": "4.63.3",
|
|
4
|
+
"head": "aea6affd6821cc7b43256c25614e42422df7177c",
|
|
5
5
|
"branch": "main",
|
|
6
6
|
"dirty": false,
|
|
7
|
-
"builtAt": "2026-10-
|
|
7
|
+
"builtAt": "2026-10-23T10:12:11.248Z"
|
|
8
8
|
}
|
|
@@ -209,6 +209,13 @@ export function SectionForm({ sections, values, onChange, onSubmit, submitLabel
|
|
|
209
209
|
// validator on every render and re-fire the effect below in a loop.
|
|
210
210
|
const onValidationChangeRef = useRef(onValidationChange);
|
|
211
211
|
onValidationChangeRef.current = onValidationChange;
|
|
212
|
+
/**
|
|
213
|
+
* The last error map actually HANDED TO the caller.
|
|
214
|
+
*
|
|
215
|
+
* 🛑 This is what stops an infinite render loop, and the loop froze the whole
|
|
216
|
+
* app — see `emitValidation` below.
|
|
217
|
+
*/
|
|
218
|
+
const lastEmittedRef = useRef(null);
|
|
212
219
|
/**
|
|
213
220
|
* Are two field-error maps the same set of messages?
|
|
214
221
|
*
|
|
@@ -232,6 +239,44 @@ export function SectionForm({ sections, values, onChange, onSubmit, submitLabel
|
|
|
232
239
|
.filter((s) => s.fields?.some((f) => keys.some((k) => k === f || k.startsWith(`${f}.`))))
|
|
233
240
|
.map((s) => s.id);
|
|
234
241
|
}, [ordered]);
|
|
242
|
+
/**
|
|
243
|
+
* Hand the result to the caller, but ONLY when it actually changed.
|
|
244
|
+
*
|
|
245
|
+
* ## 🛑 The loop this closes froze every admin editor drawer
|
|
246
|
+
*
|
|
247
|
+
* `runValidation` runs on EVERY render by design: `values` is a fresh object
|
|
248
|
+
* each time, so `ordered` → `erroringSectionIds` → `runValidation` are all
|
|
249
|
+
* new identities and the effect below re-fires. The comment there argues
|
|
250
|
+
* that is safe, and for this component's OWN state it is — `sameErrors` and
|
|
251
|
+
* the `forceOpenIds` length check both return the previous object, so React
|
|
252
|
+
* bails out.
|
|
253
|
+
*
|
|
254
|
+
* It was NOT safe for the caller. `onValidationChange` was invoked
|
|
255
|
+
* unconditionally on every run, and the real call site is
|
|
256
|
+
* `onValidationChange={() => form.validate(draft)}` — which setStates. So:
|
|
257
|
+
* render → effect → emit → caller setState → render → … forever. React
|
|
258
|
+
* reported "Maximum update depth exceeded" ~4x/second.
|
|
259
|
+
*
|
|
260
|
+
* The visible symptom was not a validation bug at all. A React tree stuck in
|
|
261
|
+
* an update loop never commits a transition, and App Router navigation IS a
|
|
262
|
+
* transition — so with the drawer open, Cancel, the X, the sort dropdown and
|
|
263
|
+
* even plain nav links all did nothing. Measured on production: `router.replace`
|
|
264
|
+
* produced ZERO `history.replaceState` calls, no error, no failed request.
|
|
265
|
+
* The only escape was a full page reload.
|
|
266
|
+
*
|
|
267
|
+
* Emitting only on a real change is also what the prop NAME promises; firing
|
|
268
|
+
* it with an identical map on every render was never meaningful to a caller.
|
|
269
|
+
*/
|
|
270
|
+
const emitValidation = useCallback((next, index) => {
|
|
271
|
+
const prev = lastEmittedRef.current;
|
|
272
|
+
if (prev && sameErrors(prev, next))
|
|
273
|
+
return;
|
|
274
|
+
lastEmittedRef.current = next;
|
|
275
|
+
onValidationChangeRef.current?.(next, index);
|
|
276
|
+
},
|
|
277
|
+
// `sameErrors` is a pure local helper; `lastEmittedRef`/`onValidationChangeRef`
|
|
278
|
+
// are refs. Nothing here needs to re-create this callback.
|
|
279
|
+
[]);
|
|
235
280
|
const runValidation = useCallback((nextValues) => {
|
|
236
281
|
const activeSchema = schema ?? inheritedSchema;
|
|
237
282
|
if (!activeSchema)
|
|
@@ -243,7 +288,7 @@ export function SectionForm({ sections, values, onChange, onSubmit, submitLabel
|
|
|
243
288
|
if (parsed.success) {
|
|
244
289
|
setFieldErrors((prev) => (Object.keys(prev).length === 0 ? prev : {}));
|
|
245
290
|
setForceOpenIds((prev) => (prev.length === 0 ? prev : []));
|
|
246
|
-
|
|
291
|
+
emitValidation({}, fieldToSectionIndex);
|
|
247
292
|
return;
|
|
248
293
|
}
|
|
249
294
|
const next = {};
|
|
@@ -266,7 +311,7 @@ export function SectionForm({ sections, values, onChange, onSubmit, submitLabel
|
|
|
266
311
|
const kept = prev.filter((id) => stillBad.has(id));
|
|
267
312
|
return kept.length === prev.length ? prev : kept;
|
|
268
313
|
});
|
|
269
|
-
|
|
314
|
+
emitValidation(next, fieldToSectionIndex);
|
|
270
315
|
}, [schema, inheritedSchema, fieldToSectionIndex, erroringSectionIds]);
|
|
271
316
|
/*
|
|
272
317
|
* 🛑 `values` MUST be a dependency, and leaving it out cost the entire admin
|