Skip to content

fix(core): keep non-nullish arrays and objects present when input is missing - #267

Open
dennev wants to merge 2 commits into
open-circle:mainfrom
dennev:fix-required-array-after-null-parent
Open

dennev wants to merge 2 commits into
open-circle:mainfrom
dennev:fix-required-array-after-null-parent

Conversation

@dennev

@dennev dennev commented Nov 12, 2026 •

Copy link
Copy Markdown

Fixes #266

setFieldInput and setInitialFieldInput set the input of an array, object or tuple to null or undefined whenever the input they were given for it was nullish, even when its schema is not nullish. initializeFieldStore keeps such a field present, and since #141 resetItemState does too. So a required array or object was undefined after reset with an initial input that omits it, after setInput with an input that omits it (the key of another variant option), and below a nullish object that setInput or reset set to null once the object became present again through one of its fields. getInput returned undefined where its type promises an array or object, and the schema rejected the input.

Both functions now treat a missing input the way initializeFieldStore and resetItemState do:

  • setInitialFieldInput checks isNullish: only a nullish field keeps a nullish initial input, and any other array, object or tuple stays present (an array without items, a tuple at its fixed length).
  • setNestedInput (in setFieldInput.ts) does the same for every field below the one the input is set on, and gives a non-nullish value field there its empty input. The field the input is set on keeps the input it is given.

As a side effect, such an array or object is no longer marked dirty only because it was given a nullish input (it can still be dirty through a changed number of items or through its fields), and a required value field below the written one whose start input is its empty input is no longer marked dirty when the input omits it (unless that empty input is NaN, which never equals itself).

Three things come with this approach. The issue describes each with its example and the alternative:

  • Value fields below the written field change too: a required string that the input omits or gives as undefined, or that is below a nullish object set to null, is now '' instead of undefined, as after reset and createForm (Add emptyInput config to form config to default empty values #149). setInput with the path of a value field and undefined still writes undefined.
  • A key that is nullish in one option of a variant or union and required in the last option that has it (one isNullish per key, Support mixed field kinds for the same path across schema branches #95) now becomes present when the input of reset or setInput omits it or gives it a nullish value, as createForm already does. Only setInput with the path of the key and a nullish value still clears it; one test guards that. Where the option that allows the key to be missing rejects the empty input (for example with v.nonEmpty()), the form now fails validation where main accepts it.
  • In a union without a discriminator, the empty inputs can make an earlier option match first, as they already do for a form created with the same input.

If you prefer another approach for any of them, I will change the pull request.

The second commit widens the first one after cubic's review finding: the first applied the check in setNestedInput only below a container that is set to a nullish value and left value fields unchanged.

Includes tests in setFieldInput.test.ts (five), setInitialFieldInput.test.ts (four), reset.test.ts (three) and setInput.test.ts (two), and changelog entries for core and methods.

This changes setNestedInput, which #261 also changes, and adds to the same changelog sections as #261, #263 and #265, so whichever is merged later needs a rebase; I will rebase this one if it is the later one.

Checks run locally on Node 24.22.2: pnpm format.check and pnpm lint pass in core, methods, every framework package and website; pnpm test passes in core, methods and every framework package that has tests; pnpm check passes in frameworks/svelte. Without the fix (only the four test files of this branch copied onto main), thirteen of the fourteen new tests fail (the fourteenth guards the kept behavior of a nullish input that is set on a field itself) and the other tests of those files pass.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed how null or omitted parent values affect nested arrays, objects, and tuples. Non-nullish nested values now remain present with appropriate empty or fixed-length values when setting input or resetting.
    • Missing nested value fields now receive their empty input, including when switching to a variant that contains those fields.
    • Updated dirty-state handling so an empty array matching the new initial input is not marked dirty.

…missing

`reset` and `setInput` could leave an array, object or tuple whose schema is
not nullish `undefined`, although `initializeFieldStore` keeps such a field
present (`true`), and so does `resetItemState` since open-circle#141:

- `setInitialFieldInput` wrote a nullish initial input to it whatever its
  schema. `reset(form, { initialInput: { name: 'Bob' } })` left every omitted
  non-nullish array, object and tuple `undefined`, while a form created with
  the same initial input holds an empty array, an object and a tuple there.
- `setFieldInput` did the same for the fields below an array or object that
  is set to a nullish value. After `setInput` set a nullish object to `null`,
  its non-nullish arrays and objects were `undefined`, and setting another
  field of that object made it present again with those keys still
  `undefined`.

In both cases `getInput` returned `undefined` where its type promises an array
or object, and the schema rejected the input with "Invalid type: Expected
Array but received undefined".

`setInitialFieldInput` now checks `isNullish` the same way the other two
functions do. `setFieldInput` does so for the fields below an array or object
that is set to a nullish value; a nullish input that is set on a field itself
is kept as before.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

Explain Change →

No actionable comments were generated in the recent review.

Walkthrough

Initial-input handling now keeps non-nullish arrays, objects, and tuples present when input is omitted or nullish. Nested input handling also preserves these containers below nullish parents and assigns configured empty inputs to omitted nested value fields. Tests cover initial input, reset, setInput, nested array items, and variant schemas.

Suggested reviewers: fabian-hiller

Severity of issue fixed: Medium

Merge Risk: Minimal · up to 0b610

This change keeps required arrays, objects, and tuples present when input is omitted or nullish. No actionable merge-blocking risk was found, and tests cover the changed behavior.

Pre-merge checks: Passed 4

Check name Status Explanation
Title check Passed The title names the main change: arrays and objects that are non-nullish stay present when input is missing, which matches the changeset's setFieldInput, setInitialFieldInput, setInput, and reset fixe…
Linked Issues check Passed Direct link #266 (open). Requirements and code at head 0b610de:
(1) reset with a partial initialInput keeps omitted non-nullish arrays, objects and tuples present [#266]. setInitialFieldInput.ts now k…
Out of Scope Changes check Passed Source changes stay in setInitialFieldInput.ts and setNestedInput in setFieldInput.ts, the two write paths named in #266. The value-field empty-input change, the dirty-state effect, and the variant/un…
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Full details: Title check

Explanation

The title names the main change: arrays and objects that are non-nullish stay present when input is missing, which matches the changeset's setFieldInput, setInitialFieldInput, setInput, and reset fixes. The 'fix(core)' scope is slightly narrow because the methods package also changes, but the title still describes the primary behavior correctly.

Full details: Linked Issues check

Explanation

Direct link #266 (open). Requirements and code at head 0b610de:
(1) reset with a partial initialInput keeps omitted non-nullish arrays, objects and tuples present [#266]. setInitialFieldInput.ts now keeps a nullish array or object input only when the store isNullish. Reported tests: reset.test.ts (three) and setInitialFieldInput.test.ts (four).
(2) A nullish container set to null, later made present through a field, keeps its required arrays and objects present [#266]. setNestedInput in setFieldInput.ts passes isNested=true to children. A nested array or object keeps nullish input only when its store isNullish. Reported tests: setFieldInput.test.ts (three) and setInput.test.ts (one).
(3) Switching a variant or union option keeps a required array present [#266]. The setInput variant test in setInput.test.ts covers switching to an option with a non-nullish array. Omitted keys below the written object now go through the same isNested path.
(4) Omitted required value fields below the written field receive their empty input, and the written field keeps its input [#266]. The diff sets valueInput to emptyInput[type] only when isNested, input is undefined and the store is not nullish. A top-level setInput with undefined keeps undefined.
The PR description bullet on variant/union keys says these keys stay undefined after setInput. That conflicts with the generic isNested path in the diff and with the issue's proposed fix. The code and the issue's stated fix take precedence. The variant test is the evidence for this route.
I did not run the test suite. Conclusions rest on the source diff of setFieldInput.ts, the issue text, and the PR description's test report.

Full details: Out of Scope Changes check

Explanation

Source changes stay in setInitialFieldInput.ts and setNestedInput in setFieldInput.ts, the two write paths named in #266. The value-field empty-input change, the dirty-state effect, and the variant/union key effects are the tradeoffs that #266's proposed fix states. Tests cover the issue's scenarios. Changelog entries in core and methods document the fix. The rebase note about #261, #263 and #265 concerns merge order, not scope. No unrelated refactoring is evident. The previous assessment covered an earlier version and was superseded for the value-field change.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Warning

Some tools did not complete. Review the errors below.

ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

packages/core/src/field/setFieldInput/setFieldInput.test.ts

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.

packages/core/src/field/setFieldInput/setFieldInput.ts

ESLint skipped: the matched ESLint configuration already failed (missing-dependency).

packages/methods/src/setInput/setInput.test.ts

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

Recent review info

Run configuration

Commits

Reviewing files that changed from the base of the PR and between df5da25 and 0b610de.

Files selected for processing: 5

  • packages/core/CHANGELOG.md
  • packages/core/src/field/setFieldInput/setFieldInput.test.ts
  • packages/core/src/field/setFieldInput/setFieldInput.ts
  • packages/methods/CHANGELOG.md
  • packages/methods/src/setInput/setInput.test.ts
Files skipped from review as they are similar to previous changes: 2

  • packages/core/CHANGELOG.md
  • packages/methods/CHANGELOG.md

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 8 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

A required array or object that is omitted from an object passed to
`setInput` was still `undefined` after the previous commit, for example the
key of another `variant` option when the discriminator is set to that option
later.

`setNestedInput` now treats a missing input of every field below the one the
input is set on the way `initializeFieldStore` and `resetItemState` do: a
non-nullish array, object or tuple stays present, and a non-nullish value
field gets its empty input. After `setInput`, the required fields that the
input omits or gives as `undefined` hold what `reset` gives them when its
initial input omits them, also below a nullish object that is set to `null`
and becomes present again. As before, `setInput` and `reset` do not apply a
default set with `v.optional(..., default)` and the like; `createForm` does.

The input of the field that is written itself is kept as it is given, so
`setInput` with the path of a key that another option of a `variant` or
`union` requires still clears it with a nullish input. A nullish value of
such a key inside an object that is passed makes it present, as `createForm`
does.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

reset and setInput leave required arrays and objects undefined when their input is omitted

1 participant