rjsf-team/react-jsonschema-form

additionalProperties: renaming a key to an empty string is silently a no-op

Open

#5,097 opened on Jun 1, 2026

View on GitHub
 (0 comments) (0 reactions) (0 assignees)TypeScript (13,175 stars) (2,136 forks)batch import
bughelp wanted

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 additionalProperties value false (closed, fixed).
  • #2462 — additionalProperties [number] doesn't allow 0 (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

  1. Render a <Form> with schema { type: "object", additionalProperties: { type: "string" } } and formData = { foo: "bar" }.
  2. Click into the "foo" key input, select all, delete, then blur (Tab or click outside).
  3. Inspect formData via onChange.

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 in handleKeyRename (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.

Contributor guide