Skip to content

Conversation

nlemoine
Copy link

Reasons for making this change

Fixes #4693

If this is related to existing tickets, include links to them as well. Use the syntax fixes #[issue number] (ex: fixes #123).

Checklist

  • I'm updating documentation
  • I'm adding or updating code
    • I've added and/or updated tests. I've run npx nx run-many --target=build --exclude=@rjsf/docs && npm run test:update to update snapshots, if needed.
    • I've updated docs if needed
    • I've updated the changelog with a description of the PR
  • I'm adding a new feature
    • I've updated the playground with an example use of the feature

@heath-freenome
Copy link
Member

heath-freenome commented Sep 20, 2025

@nlemoine I guess the TLDR is I'm not a fan of the approach, as it is too complex for your use case. I am open to you arguing your case for this approach over a simple flag to update the toIdSchema() generation code.

@nlemoine
Copy link
Author

Hey @heath-freenome, thanks for the thorough review! I really appreciate your time and feedback.

I understand your comments and will try to address your concerns, particularly about using toIdSchema instead of a separate path segments system. After careful consideration, I believe we need separate ID and name generation for both technical and practical reasons. Let me explain why.

Backend need examples

toIdSchema generates flat strings with consistent separators, but backends need structured notation that preserves the data hierarchy. This isn't just about changing separators or wrapping id segments within brackets - it's about preserving structural information and providing users to generate the name they might need. It may vary depending on the backend integration:

  • PHP/Rails: root[tasks][0][title] - brackets with or without array indexes
  • Django: root__tasks-0__title - different separators for objects vs arrays

The toIdSchema flag approach problem

Correct me if I'm wrong, but implementing a toIdSchema flag would result in idattributes being equals to name attributes.

And handling array indexes would also need changing code elsewhere because array indexes are not part of toIdSchema arguments:

const itemIdSchema = schemaUtils.toIdSchema(itemSchema, itemIdPrefix, itemCast, idPrefix, idSeparator);
// Compute the item UI schema using the helper method
const itemUiSchema = this.computeItemUiSchema(uiSchema, item, index, formContext);
return this.renderArrayFieldItem({
key,
index,
name: name && `${name}-${index}`,

I gave a quick and dirty try to that approach and it gave me:

<input id="root[listOfStrings]_0" name="root[listOfStrings]_0" class="form-control" label="listOfStrings-0" required="" placeholder="" type="text" aria-describedby="root[listOfStrings]_0__error root[listOfStrings]_0__description root[listOfStrings]_0__help" value="foo">

But most importantly, it would bring some more major issues, IMHO.

HTML Requires different IDs and names

Even with a toIdSchema working solution (e.g. generating bracketted ids), this isn't just about backend preferences - HTML itself requires different values for id and name attributes in several fundamental cases:

1. Radio button groups

Radio buttons in the same group must share the same name but have different id values:

<!-- ✅ Same name, different IDs -->
<input type="radio" id="root_color_0" name="root[color]" value="red">
<label for="root_color_0">Red</label>

<input type="radio" id="root_color_1" name="root[color]" value="blue">
<label for="root_color_1">Blue</label>

<!-- ❌ Using ID as name breaks radio grouping -->
<input type="radio" id="root_color_0" name="root_color_0" value="red">
<input type="radio" id="root_color_1" name="root_color_1" value="blue">

Radio group is already ok in current RJSF state but if id is used as name, this will no longer be the case.

2. Checkbox arrays

Multiple checkboxes that submit as an array need the same base name:

<!-- ✅ PHP-style array submission -->
<input type="checkbox" id="root_hobbies_0" name="root[hobbies][]" value="reading">
<input type="checkbox" id="root_hobbies_1" name="root[hobbies][]" value="gaming">
<input type="checkbox" id="root_hobbies_2" name="root[hobbies][]" value="cooking">
<!-- Submits as: hobbies = ['reading', 'gaming'] when multiple are checked -->

<!-- ❌ Using ID as name -->
<input type="checkbox" id="root_hobbies_0" name="root_hobbies_0" value="reading">
<input type="checkbox" id="root_hobbies_1" name="root_hobbies_1" value="gaming">
<!-- Submits as separate unrelated values, not an array! -->

Same as above.

3. Select multiple

Similar issue with multi-select dropdowns:

<!-- Needs array notation in name -->
<select id="root_skills" name="root[skills][]" multiple>
  <option value="js">JavaScript</option>
  <option value="python">Python</option>
</select>

Why brackets in IDs are complicated

Even if we wanted to use the same bracketed format for both ID and name (like root[tasks][0]), it would break or weaken major parts of the web stack:

CSS selectors

/* Brackets are special characters in CSS */
#root[tasks][0] { }  /* ❌ BREAKS - interpreted as attribute selector */

/* Would require escaping everywhere */
#root\[tasks\]\[0\] { }  /* Ugly and error-prone */

/* Current RJSF approach */
#root_tasks_0 { }  /* ✅ Clean, simple, works everywhere */

JavaScript DOM Queries

// querySelector fails with special characters
document.querySelector('#root[tasks][0]')  // ❌ SyntaxError

// Would need escaping
document.querySelector('#root\\[tasks\\]\\[0\\]')  // Complex escaping

// Current clean approach
document.querySelector('#root_tasks_0')  // ✅ Works everywhere

I would have loved a much simpler solution too. Adding a flag in toIdSchema feels like a workaround more than a robust and flexible alternative.

I tried to think of a one that brings the minimal possible changes. It can surely be improved (JS/React is not my strong suit) and I will address your review comments if you change your mind.

However, if you are still not convinced and don't see any benefits in bringing separate logic for id and name attributes, I would totally understand. I'm ready to work on a toIdSchema alternative, since I'd really like that feature to make it for version 6.

What do you think? I'll be happy to refactor the implementation based on your preferences.

@heath-freenome
Copy link
Member

heath-freenome commented Sep 30, 2025

@nlemoine Thank you for the well documented explanation. It has convinced me and my compatriot @nickgros that there is merit in your approach. So much so, that I've just pushed a PR that does a major breaking refactor of the system to simplify your work quite a bit.

In order to implement your feature, I am going to recommend the following approach after my PR merges:

  1. Add the nameGenerator to the GlobalFormOptions as optional
  2. Update the FieldPathId to add an optional name variable
  3. Update the toFieldPathId() function to generate the name if the nameGenerator exists (you can check the type of the element in the path prop to determine object or array)
  4. Update the WidgetProps to add the optional htmlName prop
  5. Update everywhere the <Widget ...> is used in the codebase to add htmlName={fieldPathId.name}
  6. Update all of the widget implementations in the codebase to add htmlName || id for the name props
  7. Update all of the tests to deal with that change
  8. Update the docs to add the optional htmlName to the Widget docs
  9. Update the v6.x migration guide to describe this new feature as well as let custom widget developers know about the htmlName props

@nlemoine
Copy link
Author

nlemoine commented Oct 3, 2025

HI @heath-freenome,

Thank you so much for paying so much attention to that issue and bringing so much changes to that purpose! 🙌

I'll try to update the PR as soon as I can and will keep posted.

@nlemoine nlemoine force-pushed the feat-name-generator branch from 943b7d0 to c921892 Compare October 6, 2025 19:27
@nlemoine nlemoine force-pushed the feat-name-generator branch from c921892 to 621a1fa Compare October 6, 2025 19:29
@nlemoine
Copy link
Author

nlemoine commented Oct 6, 2025

Quick questions @heath-freenome

  • Do I have to update every RJSF theme?

  • I'm having an issue the current implementation doesn't solve. When displaying array as checkboxes, there is no index and the elementType is still an object. So no bracket is generated (e.g. root[multipleChoicesList][]). I currently don't have the information that tells me if I should use brackets or not. Do you have any idea for that use case?

  • I noticed that you've provided some sample implementations of these in utils. If someone wanted to just use them straight up, but with a different rootName they could wrap them?

    Yes they could but say you want to output multiple forms on the same page and want different root names, you will have to create multiple wrapper functions to get this done, which is quite ugly. However, I would be fine reusing the idPrefix as a parameter of the name generator function to provide a custom root name. Imagine the same logic for Ids, wrapping toIdSchema for each customization would be cumbersome.

  • Do you want me to remove initially provided nameGenerator functions (in utils, bracket/dot notation you mentioned in the quote above)? I just committed them to enable an easier playground preview but I would be fine and they're not part of RJSF.

@heath-freenome
Copy link
Member

heath-freenome commented Oct 6, 2025

Quick questions @heath-freenome

  • Do I have to update every RJSF theme?
    Yes, every theme will need updating, sorry. New features need to work across all published themes
  • I'm having an issue the current implementation doesn't solve. When displaying array as checkboxes, there is no index and the elementType is still an object. So no bracket is generated (e.g. root[multipleChoicesList][]). I currently don't have the information that tells me if I should use brackets or not. Do you have any idea for that use case?

Right now the optionId() utility function in the CheckboxesWidget implementations are adding the -{index#} notations. Those are ids, not names. The name should be the same for all of the checkboxes, so simply passing htmlName | id still works fine.

  • I noticed that you've provided some sample implementations of these in utils. If someone wanted to just use them straight up, but with a different rootName they could wrap them?
    Or you could make the name generator take the idPrefix as an optional parameter.

    Yes they could but say you want to output multiple forms on the same page and want different root names, you will have to create multiple wrapper functions to get this done, which is quite ugly. However, I would be fine reusing the idPrefix as a parameter of the name generator function to provide a custom root name.
    Right now the only solution people have to this anyway is to render two different Form elements passing different idPrefix values, so let's just stick with that

  • Do you want me to remove initially provided nameGenerator functions (in utils, bracket/dot notation you mentioned in the quote above)? I just committed them to enable an easier playground preview but I would be fine and they're not part of RJSF.

I'm fine with NOT providing them. You will still need something to test with so perhaps adding them is helpful?

@nlemoine
Copy link
Author

nlemoine commented Oct 6, 2025

Right now the optionId() utility function in the CheckboxesWidget implementations are adding the -{index#} notations. Those are ids, not names. The name should be the same for all of the checkboxes, so simply passing htmlName | id still works fine.

Actually, no, multiple checkboxes are considered an array. So they need brackets (root[multipleChoicesList][] or root[multipleChoicesList][0]).

The name is only generated in nameGenerator and in the case I showed you, the path arg only contains ['multipleChoicesList']. With that single arg, I have no way to tell (inside nameGenerator) if I should add brackets or not. Once in widget, it's "too late" to change the name (no optionId way). That's why my first implementation added indexes as an arg of the name generator.

@heath-freenome
Copy link
Member

Right now the optionId() utility function in the CheckboxesWidget implementations are adding the -{index#} notations. Those are ids, not names. The name should be the same for all of the checkboxes, so simply passing htmlName | id still works fine.

Actually, no, multiple checkboxes are considered an array. So they need brackets (root[multipleChoicesList][] or root[multipleChoicesList][0]).

The name is only generated in nameGenerator and in the case I showed you, the path arg only contains ['multipleChoicesList']. With that single arg, I have no way to tell (inside nameGenerator) if I should add brackets or not. Once in widget, it's "too late" to change the name (no optionId way). That's why my first implementation added indexes as an arg of the name generator.

I'm not sure I understand why the indexes are needed for the name of checkboxes in a checkbox group? Since submitting the form will automatically cause the html to return name=X&name=Y&name=Z to the submitted form, which, using standard HTML parsing should result in name=[X,Y,Z]

@nlemoine
Copy link
Author

nlemoine commented Oct 7, 2025

I'm not sure I understand why the indexes are needed for the name of checkboxes in a checkbox group?

In PHP at least, this makes a huge difference when getting posted data (same name VS same name[]):

Capture d’écran 2025-10-07 à 21 19 12 Capture d’écran 2025-10-07 à 21 19 39

Long story short: when field(s) posted values are an array, they must use brackets (set of checkboxes, select with multiple attribute).

@heath-freenome
Copy link
Member

heath-freenome commented Oct 7, 2025

I'm not sure I understand why the indexes are needed for the name of checkboxes in a checkbox group?
...
Long story short: when field(s) posted values are an array, they must use brackets (set of checkboxes, select with multiple attribute).

My suggestion would be to create a new toOptionName<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>(id: string, htmlName: string, registry: Registry<T, S, F>, index: number): string function that uses the nameGenerator to append the [index] onto the end of the htmlName as needed for the index, when htmlName and nameGenerator exist, otherwise simply return htmlName | id. Then update all the widgets that need it to use it for the name.

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.

Compliant form elements names for "old" submitted data (POST)
2 participants