fix(SelectWidget): use real enum values for option values when nameGenerator is active#5004
Conversation
|
@nlemoine Help me understand the problem here. How does the name generator break things? The select widget converts to/from the index value upon selection. so the caller is still given the proper value. |
|
@heath-freenome Sorry, I should have included a playground example. See the option values: ❌
<select id="root_status" name="root_status" role="combobox" class="form-control" aria-describedby="root_status__error root_status__description root_status__help">
<option value=""></option>
<option value="0">active</option>
<option value="1">inactive</option>
<option value="2">archived</option>
</select>When submitting the traditional way (the whole PR I worked on), the submitted values are wrong. ✅
<select id="root_status" name="root_status" role="combobox" class="form-control" aria-describedby="root_status__error root_status__description root_status__help">
<option value=""></option>
<option value="active">active</option>
<option value="inactive">inactive</option>
<option value="archived">archived</option>
</select>Let me know if you need more details. |
e2adff1 to
0a412e9
Compare
But the onChange handler should do the conversion so that the onSubmit will get the proper data in the |
All the work done in #4773 has been made to that only purpose: be able to submit a form using standard HTML. I know people forget about it now everything is JS based :) In a classic form submission (no JS involved): no |
AHHH.... Got it. Let me take a closer look then :) |
|
Thanks for your feedback and comments @heath-freenome, I addressed them all. There's probably some room for DRYness here too. I'll handle all this in #5000 |
|
Mapping via If you use Also, if someone already relies on the current behavior, this would be a breaking change. |
676ba57 to
b9bf68d
Compare
|
Thanks for the feedback @x0k, that's a good catch and I reworked the approach. Right now, I realized it not only affects Rather than overloading value for both jobs, I split them:
Handlers read data-index and call
This restores What do you think? If you're ok with this, I can spread this approach to |
This is what I meant. With the current approach on the server:
If this approach is acceptable for your project, implement it as a custom widget. In my opinion, to properly support using RJSF with native forms, it’s better to approach this differently - namely, implement |
|
For objects/arrays, native form submission relies on proper field naming conventions. That's what <input type="checkbox" name="pets[]" value="dog" />
<input type="checkbox" name="pets[]" value="cat" />Will generate a PHP array: $_POST['pets'] = ['cat', 'dog'];For the 123 vs '123' distinction, this is how HTML forms have always worked. Every value hits the server as a string. The server parses based on the expected type. That's true for too, it sends "123", not the number 123. The data-index approach in this PR keeps the JS-side typed resolution intact (no behavior change for existing RJSF users) while making the HTML output correct for standard form submission. There's also an accessibility/UX angle. Browser autofill depends on |
|
I would hope that people who are using the nameGenerator aren't dealing with complex objects like @x0k has in his example. @nlemoine I mostly agree with the approach you are taking and hope that the code can be a DRY as possible (maybe a new utility method or 2 would be helpful?). I would also love to have this solution working across all themes and places where we did the index replacement (to solve the issues with typing). And I am intrigued by the suggestion of adding a Form/UiSchema prop that controls whether JSON.stringify/parse is used to render/select values in the components. Maybe an optional prop on the |
|
Thanks for the clarification @x0k and the example. Sorry, I didn't spot the object enum right away.
That said, you're right that the current index-based approach is at least harmless for object enums, while String(value) would produce "[object Object]". We could fall back to the index/stringyfied for non-primitive values to avoid that regression.
Every HTML form input sends strings.
Fair but honestly, very unlikely.
Open to that, though I wonder if it adds complexity for a niche case. The data-index attribute is always present, so anyone who needs index-based resolution can access it. Making real values the default aligns with how HTML forms work everywhere else. To summarize:
Proposed alternative:
What do you think? (@heath-freenome, I'll refactor the logic, just need feedback and approval from both of you about the behavior) |
b9bf68d to
ab86c06
Compare
|
Implementing this as an opt-in feature (with the option to switch to opt-out in v7) is a good solution.
I don’t think this is a good idea, as it’s unclear how to handle this on the server. For an opt-in feature,
I think using the UI schema would result in a more flexible solution, as it would allow controlling behavior both for individual fields and for the entire form (
This doesn’t seem like a universal solution. It’s easy to imagine UI libraries that don’t allow passing interface EnumValueMapper {
fromValue: (value: JSONSchema7Type | undefined) => string;
toValue: (value: string) => JSONSchema7Type | undefined;
}Using it, you can create options with the desired encoding (indices, |
@x0k Can you point to how your library is defining and using it by providing links for us to checkout? |
|
|
Thanks for the links @x0k, very helpful to see how you approached this in your Svelte library. Our current implementation follows a very similar pattern:
Both are standalone utility functions in Configuration is via // Form-wide
uiSchema: {
"ui:globalOptions": { useRealOptionValues: true }
}
// Per-field
uiSchema: {
myField: { "ui:options": { useRealOptionValues: true } }
}When disabled (default): index-based encoding, fully backward compatible. Decoder does a reverse lookup by matching All existing tests pass across all packages. Will push the updated branch shortly. |
1ab5c5c to
8a331f9
Compare
|
Branch updated, I'll let you check it out and give me your feedback! |
heath-freenome
left a comment
There was a problem hiding this comment.
@nlemoine Great work in responding to the feedback and improving your code. I've made note of a few more places where you can DRY up repetitive code. Also some of the snapshots seems like things were incorrectly changed. Plus your tests are failing in several themes.
95abee6 to
d95118c
Compare
|
This should be good now, CI is green. |
heath-freenome
left a comment
There was a problem hiding this comment.
@nlemoine Great work here. Thanks for being patient with all the suggested changes. I have a few more suggestions to help maintainability.
Also, it also looks like you skipped over converting the antd theme?
…ndex for typed resolution Replace index-based <option value="0"> with real enum values <option value="real_value"> so that native form submission sends meaningful values. Add data-index attribute to preserve typed value resolution via enumOptionsValueForIndex in JS handlers. Scope limited to core and react-bootstrap (native <select> packages). Custom component packages (chakra, mui, mantine, etc.) are unchanged as they don't participate in native form submission.
Add enumOptionValueEncoder/Decoder utilities to @rjsf/utils that allow rendering real enum values in option attributes instead of array indices. Controlled via ui:globalOptions or per-field ui:options. When enabled, primitives use String(value) for DOM attributes with reverse lookup decoding. Objects/arrays fall back to index encoding. Default behavior (index-based) is fully preserved. Applied across SelectWidget, RadioWidget, and CheckboxesWidget in all 10 theme packages.
…ed to strings before processing
Co-authored-by: Heath C <51679588+heath-freenome@users.noreply.github.com>
Co-authored-by: Heath C <51679588+heath-freenome@users.noreply.github.com>
Co-authored-by: Heath C <51679588+heath-freenome@users.noreply.github.com>
…d new utilities for select options
23dfb16 to
9b31c5a
Compare
Replace the boolean flag with a 'indexed' | 'realValue' union on GlobalUISchemaOptions for consistency with other option-style APIs in RJSF. Add an OptionValueFormat type and update the encoder, decoder, and selectedValue utilities to take a format parameter that defaults to 'indexed'.
|
Done. Both paths go through one |
…for Chakra and DaisyUI SelectWidgets
Replace the `Component = multiple ? MultiSelect : Select` pattern with two explicit JSX branches sharing a common props object. Each branch passes its own narrow value type (`string[]` for MultiSelect, `string | null` for Select), dropping both the `as any` cast and the `multiple ? [] : null` conditional on enumOptionSelectedValue's emptyValue argument.
|
@heath-freenome I addressed all comments from your review. With slightly different approaches on some of them. I posted the details to show the reasoning behind decisions. Supporting so much UI libs, with all their distinctive implementation details, is quite a challenge 😅 Let me know if you spot something in the updated code. |
heath-freenome
left a comment
There was a problem hiding this comment.
@nlemoine Nice work! I have a couple of questions/suggestions for you. Plus there are conflicts in the CHANGELOG.md again (sorry, merging other fixes).
| <Select | ||
| {...sharedProps} | ||
| value={enumOptionSelectedValue<S>(value, enumOptions, false, optionValueFormat, null) as string | null} | ||
| onChange={!readonly ? handleChange : undefined} |
There was a problem hiding this comment.
I'm curious if there was a reason the onChange() handler is not in the sharedProps?
There was a problem hiding this comment.
Yeah, no real reason. I just missed it when I split the JSX. onChange has the same !readonly ? handleChange : undefined gate as onBlur and onFocus, so it belongs in sharedProps with them. Moved it in.
|
@nlemoine Also, a new conflict in the MUI select widget due to another PR merging |
|
@nlemoine Just a few conflict resolutions and a small change and this PR is good to go. How can I help? |
|
@heath-freenome Should hopefully be all good now :) I'll be off next week and won't be able to make any more changes until April 27 at best. Feel free to take over if changes are needed and the PR blocks next release. |

Reasons for making this change
When htmlName is present (nameGenerator enabled), SelectWidget rendered option values as array indices (0, 1, 2) instead of real enum values (e.g. "active", "inactive"). This broke native form submission since the POSTed value was the index, not the actual value. Fixes all 10 themes.
Related to
nameGeneratorintroduced in #4773Checklist
npx nx run-many --target=build --exclude=@rjsf/docs && npm run test:updateto update snapshots, if needed.