Repository navigation
fix(jsonforms-renderers): show required asterisk for conditionally required fields - #112
Conversation
Conditional schema required (allOf if/then) does not set ControlProps.required, so fields that are only mandatory when visible never get the red *. Allow uischema.options.required to force the same marker TextControl/NumberControl already use for schema-required fields. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configuration
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. Comment |
Same escape hatch as TextControl/NumberControl for fields that are only conditionally required (allOf if/then). Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Thanks for picking this up! I'd like to suggest a different approach before we merge.
Scenario
In the SLTB Blend Sheet Review form, the officer first picks a Review Outcome. Only when it's "Approve blend sheet & Assess Levy" are SLTB Blendsheet Reference Number and Assessed SLTB Levy (LKR) shown, and only then are they mandatory. The bug report asks for three things:
- Show the red
*on these fields when they're required. - Reject negative values and characters like
+/-in the levy field. - Accept only positive amounts (e.g.
1000,1000.50).
Concerns with options.required
- The rule ends up in two places. Whether a field is required is already defined in the JSON Schema (
allOf/if/then). Addingoptions.required: truein the uiSchema repeats it just for the label. The two can drift: a field could show*without actually being required, or the other way round. - Items 2 and 3 aren't addressed.
NumberControlstill renders<input type="number">with nomin, so-,+andeare still accepted.
Suggested approach
The JSON Schema already holds everything we need, so the renderer should read it rather than take a separate flag:
"assessedLevy": { "type": "number", "exclusiveMinimum": 0, "multipleOf": 0.01 },
"allOf": [{
"if": { "properties": { "reviewOutcome": { "const": "<approve-value>" } }, "required": ["reviewOutcome"] },
"then": { "required": ["sltbReference", "assessedLevy"] }
}]The only gap is in @jsonforms/core: its isRequired() reads just the parent's plain required array and ignores if/then (checked in 3.8.0), so ControlProps.required comes through as false. AJV's errors can't drive the * either, because the required error goes away as soon as the field is filled. Instead of a uiSchema flag:
-
Add a small shared
useIsRequiredhook that asks AJV. Validate a copy of the current data with this field removed, using the form's own AJV (useJsonForms().core.ajv). If AJV reports arequirederror whosemissingPropertyis this field (under the matching parent path), the field is required right now:const withoutField = unset(cloneDeep(rootData), path) validate(withoutField) const isRequired = validate.errors?.some( e => e.keyword === 'required' && e.params.missingProperty === fieldName /* + matching parent path */ )
This avoids parsing
allOf/if/thenby hand. It works for any construct (if/then/else,oneOf,dependentRequired, nesting) and agrees with validation by definition: the*shows exactly when submitting the field empty would fail. Memoize it on the root data. Text, Number, Radio and Select controls would all use it, OR-ed with the staticrequiredprop. -
Tighten
NumberControl. Passminfromschema.minimum/exclusiveMinimum. When the minimum is ≥ 0, block the-,+,e,Ekeys and reject pasted values containing them. Surface theexclusiveMinimum/multipleOferrors throughgetErrorMessage. -
Update the form definition (outside this repo): add
exclusiveMinimum: 0(andmultipleOf: 0.01if we want at most 2 decimals) to the levy field, and make sure both fields are inthen.required.
This keeps the schema as the single source of truth for validation and for the required marker.
Replace uiSchema options.required with a shared useIsRequired hook that asks the form validator whether the field is required under current data, and tighten NumberControl with min/step plus blocking of signed keys. Co-authored-by: Cursor <cursoragent@cursor.com>
|
@mushrafmim Thanks, switched away from |
Co-authored-by: Cursor <cursoragent@cursor.com>
mushrafmim
left a comment
There was a problem hiding this comment.
Using AJV to check whether removing the field produces a required error is the right approach: the schema stays the only source of truth, and conditionals don't need to be parsed by hand. Two things before merging:
Some controls still use the old check. Boolean, Date, SearchSelect, File, Xml and Spreadsheet controls still render required && <Text color="red">*</Text>. As a result, a field that becomes required through if/then gets a * when it's a text, number, radio or select input, but not when it's a date or SearchSelect. Could we use useIsRequired in those controls too, so every control shows the asterisk the same way? (See also the performance note inline.)
… possible Wire the AJV-backed asterisk into Boolean, Date, SearchSelect, File, Xml and Spreadsheet, and avoid per-keystroke clone/validate when the control is hidden or the root schema has no conditional required constructs. Co-authored-by: Cursor <cursoragent@cursor.com>
|
LGTM! |
mushrafmim
left a comment
There was a problem hiding this comment.
Thanks, both earlier comments are addressed: every control now uses useIsRequired, and the AJV check is skipped for hidden fields, statically required fields and schemas with no conditional keywords. One small non-blocking note inline that's fine to handle in a follow-up.
sthanikan2000
left a comment
There was a problem hiding this comment.
Requesting changes for the split and the description fixes below. The inline comments are marked where they are non-blocking.
@ginaxu1 thanks for the quick iterations. Before this merges, could we split it into two PRs?
The code is fine to review together (about 175 non-test lines). The problem is release notes: each package's notes list one PR as one entry under one label, and this PR carries two changes a consumer would notice:
- Conditional-required asterisk: the
useIsRequiredhook,required.ts, and the wiring in all 10 controls. This is a bug fix. - NumberControl constraints:
min/stepfrom the schema, blocking-/+/e/Eon keydown and paste, and new wording for theexclusiveMinimum/minimum/multipleOferrors.
Proposal
- #112 stays as (1). Remove from this branch the
min/step/onKeyDown/onPastehunks inNumberControl.tsx(keep theuseIsRequiredlines),error.tsanderror.test.ts, and the second test inNumberControl.test.tsx. Retitle tofix(jsonforms-renderers): show required asterisk for conditionally required fields. - (2) becomes a new PR from
main:fix(jsonforms-renderers): NumberControl honours minimum, exclusiveMinimum and multipleOf. Whichever merges second needs a small rebase, since both touch theNumberControl.tsximports.
Description fixes for #112
- It still says the hook is wired into "Text, Number, Radio, and Select". It's all ten controls now.
- Please drop the
options.requiredhistory; it has no place in a squash commit. - Please fill in the template's Release notes section, which the release skill reads. Suggested wording: Fields that become required through
if/then,oneOfordependentRequirednow show the red*in every control, only while the condition holds.
For the second PR's release note: Number fields with a minimum/exclusiveMinimum >= 0 no longer accept -, +, e or E, and use min/step from the schema. Error messages now read "X must be greater than 0" instead of "must be > 0". The wording change is visible to anyone matching on the old strings.
Playground fixtures (as in #95 and #101): no fixture currently uses allOf/if/then. Add a required-conditional fixture to dev/fixtures.ts in #112: a Review Outcome radio at the root, with then.required covering text, number, select, date and boolean fields. In the new PR, extend the existing number fixture with a levy field (exclusiveMinimum: 0, multipleOf: 0.01).
…t-only scan Assert preventDefault via fireEvent return values (including paste), and note that schemaHasConditionalRequired only inspects the root schema's own keys. Co-authored-by: Cursor <cursoragent@cursor.com>
|
@ginaxu1 thanks. I checked b1eb25b: the hardened test now fails without the handlers, the doc line is in, and #115/#116 cover the follow-ups. What's still open from my review is the split. #112 still carries the NumberControl changes alongside the asterisk fix. #117 doesn't touch NumberControl, so it's unaffected. Could you remove those parts here and open them as a PR from I'll re-review right after. |
…only Move NumberControl min/step/key/paste and exclusiveMinimum error wording out of this PR, and add a required-conditional playground fixture. Co-authored-by: Cursor <cursoragent@cursor.com>
|
@sthanikan2000 Split done:
|
sthanikan2000
left a comment
There was a problem hiding this comment.
Thanks @ginaxu1, the split looks right: #112 is asterisk-only, NumberControl is back to the three useIsRequired lines, and the fixture works. I rendered it: all five controls show * when approved (empty or filled) and none on reject.
I edited the description, so please don't overwrite it:
- Release notes. They said fields become required through
if/then,oneOfordependentRequired. I checked each against JSONForms'createAjv:if/thenworks;dependentRequiredreports nothing, so it isn't enforced and no*shows;dependenciesis enforced but reported under its own keyword, souseIsRequiredignores it;oneOfalternatives ([{required:['a']},{required:['b']}]) put a*on both fields. The note now names onlyallOf/if/thenand the ten controls that are wired. Closes #115 (via #117)is nowRefs #115 (fixed by #117), so #115 doesn't close before the nested fix lands.
Please merge #117 before the version bump: the nested/$ref part of the Release notes is only true once it's in. Suggested order: #112, #117, #119 (the last two will each need a rebase onto main).
Non-blocking follow-up: dependencies is enforced by AJV but ignored by the hook, because AJV reports it under the dependencies keyword instead of required.
Use the #112 hook for conditional required, keep the separate minItems asterisk rule, limit options.errorMessage to empty arrays, and expose both in the array playground fixture. Co-authored-by: Cursor <cursoragent@cursor.com>
Summary
@jsonforms/core'sisRequired()only reads the parent's plainrequiredarray, so fields that become mandatory underallOf/if/thennever get the red*. This PR derives the asterisk from the form schema via AJV.Type of change
Changes made
useIsRequiredhook: ORs JSONForms' staticrequiredprop with an AJV check (clone root data, unset the control path, look for a matchingrequired/missingPropertyerror).$reffollow-up: fix(jsonforms-renderers): scan nested and $ref schemas for conditional required #117).required-conditionalfor reviewing the asterisk underif/then.NumberControl min/step/key blocking and constraint error wording live in #119.
Testing
pnpm test(focused NumberControl + required utils)required-conditionalfixture in the package playgroundRelease notes
Fields that become required through
allOf/if/then(including in nested objects and local$refs) now show the red*on text, number, date, boolean, select, radio, search-select, file, XML and spreadsheet controls, only while the condition holds.Related issues
Refs #115 (fixed by #117)