onChange debounce is never cancelled on unmount, so JsonForms emits after the form is gone
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 78/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Quiet
- Tech stack
- react, typescript
- Domain
- frontend
Research direction
Start in packages/react/src/JsonFormsContext.tsx around JsonFormsStateProvider and the debounced onChange emit at lines 258-263. Read the existing debounce and effect lifecycle, then verify the relevant React package tests or reproduce the reported unmount sequence. Done means unmounting within the 10 ms window produces no later onChange callback and avoids the reported React 19/jsdom teardown error.
Written by the indexing model from the issue text.
Description
Describe the bug
JsonFormsStateProvider debounces its onChange emit by 10 ms but never cancels the pending timer when the component unmounts. A trailing edge scheduled just before unmount still fires afterwards, invoking the consumer's onChange when the form is already gone.
const debouncedEmit = useCallback(
debounce((...args: any[]) => onChangeRef.current?.(...args), 10),
[]
);
useEffect(() => {
debouncedEmit({ data: core.data, errors: core.errors });
}, [core.data, core.errors]);
There is no cleanup effect, and nothing calls debouncedEmit.cancel().
The debounce itself is well-motivated (#1150, and the comment above it explains the Chrome-autofill rerender chain). This report is only about the missing cancel on unmount.
Expected behavior
Once <JsonForms> unmounts, it should not call onChange again. A consumer that unmounts a form cannot reasonably expect a change callback for it up to 10 ms later.
Steps to reproduce the issue
- Render
<JsonForms … onChange={spy} />. - Fire a change on any control.
- Unmount within the 10 ms debounce window.
- Wait > 10 ms —
spyreceives one more call, after unmount.
Impact we hit
Two flavours, one benign and one not:
- In the browser: a state update and a parent callback up to 10 ms after unmount. Usually harmless, but it is a real "write after teardown" — for us it meant a parent's
onDataChangefiring for a form the user had already navigated away from. - In tests (the expensive one): under React 19 + jsdom, the late emit reaches React's
dispatchSetStateafter the test environment is torn down, throwing an unhandledReferenceError: window is not defined. Vitest reports this as an unhandled error, so the run fails while every test passes — a red CI with a green test summary, which is very hard to attribute.
ReferenceError: window is not defined
❯ resolveUpdatePriority react-dom-client.development.js
❯ dispatchSetState react-dom-client.development.js
❯ <consumer onChange handler>
❯ packages/react/lib/jsonforms-react.cjs.js:217
❯ invokeFunc → trailingEdge → Timeout.timerExpired lodash/debounce.js
Consumers can work around it by guarding their handler with an isMounted ref (that is what we did), but every consumer has to know this independently.
Suggested fix
Cancel on unmount:
useEffect(() => () => debouncedEmit.cancel(), [debouncedEmit]);
If flushing is preferred over dropping, debouncedEmit.flush() would at least run it while the tree is still alive — though for the jsdom case dropping is the safer default.
(Side note, unrelated to the bug: useCallback(debounce(...), []) constructs a new debounced function on every render and discards it, so the linter's exhaustive-deps rule is being worked around; useMemo/useRef would express the intent more directly.)
Environment
- JSON Forms: 3.8.0 (latest) — also present on
masterand in3.9.0-alpha.1, checked 2026-08-10 - Framework: React 19.2
- Renderer set: custom (Mantine) — but this is framework-agnostic, it is in
@jsonforms/reactcore
- Dominant language
- TypeScript
- Stars
- 2.7k
- Forks
- 423
- Avg merge
- 17d 8h
- Merged PRs (30d)
- 1
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from eclipsesource/jsonforms
-
Difficulty 1/5 Under an hour Newbie friendliness 88/100
eclipsesource/jsonforms#2624 ·
-
core
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
eclipsesource/jsonforms#2570 · 1 comment ·
-
angular core react vue
Difficulty 4/5 3-5 days Newbie friendliness 64/100
eclipsesource/jsonforms#2627 ·
-
react-material: date/time pickers wire `autoFocus`, `onFocus` and `onBlur` to an aria-hidden input Openmaterial react
Difficulty 3/5 1-2 days Newbie friendliness 78/100
eclipsesource/jsonforms#2615 ·
-
Difficulty 3/5 1-2 days Newbie friendliness 45/100
eclipsesource/jsonforms#2599 · 1 comment ·
All issues in eclipsesource/jsonforms
Similar issues
-
calcite-components needs triage refactor
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
Esri/calcite-design-system#15203 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 91/100
-
community first-timers-only good first issue hacktoberfest help wanted low hanging fruit up-for-grabs
Difficulty 1/5 Under an hour Newbie friendliness 95/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Automattic/studio#4908 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 90/100