additionalProperties: renaming a key to an empty string is silently a no-op
#5,097 opened on Jun 1, 2026
Description
Description
In handleKeyRename (v6 / main), and equivalently in onKeyChange on the v5 branch, the rebuild step that emits the renamed formData uses a JS || short-circuit:
const newKeys: GenericObjectType = { [oldKey]: actualNewKey };
const keyValues = Object.keys(newFormData).map((key) => {
const newKey = newKeys[key] || key;
return { [newKey]: newFormData[key] };
});
When a user clears the key input of an additionalProperty entry and blurs, handleKeyRename(oldKey, "") runs. getAvailableKey("", formData) returns "" (no conflict), so newKeys = { [oldKey]: "" }. The map step then evaluates "" || oldKey → falls back to oldKey. The rename silently no-ops: onChange fires, but formData is structurally identical to its previous value. No error, no warning, no visible feedback distinguishing "user did nothing" from "user cleared the key."
The DOM key input is uncontrolled (defaultValue={label}), so the blank text persists visually in the input until the next remount — which masks the no-op even longer. Downstream consequences vary; in our case the user saw "the deleted key came back" because save handlers persisted the old key and the row was rendered again on reload.
This is in the same family as several previously-fixed additionalProperties falsy-value bugs:
- #1412 — type-guessing fails on
additionalPropertiesvaluefalse(closed, fixed). - #2462 —
additionalProperties [number]doesn't allow0(closed, fixed).
Same root cause family (JS truthiness on a falsy-but-valid value), different code path. The path in handleKeyRename / onKeyChange was missed.
Steps to Reproduce
- Render a
<Form>with schema{ type: "object", additionalProperties: { type: "string" } }andformData = { foo: "bar" }. - Click into the
"foo"key input, select all, delete, then blur (Tab or click outside). - Inspect
formDataviaonChange.
Expected behavior
The rename takes effect — formData === { "": "bar" } — or, if the project prefers to disallow empty keys, the rename is explicitly rejected with visible feedback. Either is reasonable; the current silent no-op is not.
Actual behavior
formData is unchanged: { foo: "bar" }. The DOM key input continues to show blank until the next remount.
Version
@rjsf/core@5.9.0— reproduced.5.24.13(latest 5.x) — same code, same bug.main(v6.x) — same expression inhandleKeyRename(packages/core/src/components/fields/ObjectField.tsx).
Suggested fix
Replace the || falsy short-circuit with an explicit own-key check, using the Object.hasOwn helper already used elsewhere in this codebase (packages/utils/src/getTemplate.ts:23):
- const newKey = newKeys[key] || key;
+ const newKey = Object.hasOwn(newKeys, key) ? newKeys[key] : key;
PR coming.