Pro Forms Repeater populated from ACF ticks the first choice, not the saved one

Submitted by: HHH Consulting
Component: Pro Forms (nestable) — Repeater + Radio / Checkbox Severity: High — a saved choice is shown as a different choice, so an edit form displays the wrong value
Type: Front-end JS / syncRepeaterFields


Environment

Bricksforge 4.0.0
Bricks 2.4
WordPress 7.1.1
PHP 8.2.33
ACF PRO 6.8.10
Browser Chromium (reproduced independently of layout CSS)
Theme Bricks Child Theme 1.1

Observed on a live edit form whose Repeater dataSource is ACF. The same Radio group outside a populated Repeater is not affected, because this path only runs from populateRepeaterData.


Summary

When a Repeater is filled from ACF (dataSource: acf), syncRepeaterFields() writes each subfield onto the matching control.

For a radio or checkbox it does not look for the input whose value equals the saved value. It takes the first input in that control and sets checked = !!value.

Any non-empty saved value therefore ticks the first choice. Previous is shown as Current when Current is the first radio. Step or Adopted is shown as Biological when Biological is the first radio.

A select in the same row is handled differently (element.value = savedValue) and can show the right option when the saved value is a plain id.


Steps to reproduce

  1. In ACF, create a Repeater. Add a Radio subfield (choices Current and Previous, return format Value) or a Checkbox subfield with the same choices.
  2. Save a post with one repeater row and the choice set to Previous.
  3. In Bricks, add a Pro Form.
  4. Add a Repeater field:
  • dataSource = ACF
  • fieldName = the repeater field key or name
  • postId = the post from step 2
  1. Inside the Repeater, add a Radio wrapper whose Custom ID matches the subfield name. Add two radios, values Current and Previous, with Current first.
  2. View the page on the front end for that post.

Expected: Previous is ticked.

Actual: Current is ticked. The repeater’s data-brf-repeater-data still contains Previous.

Control: a Radio group that is not inside an ACF-populated Repeater is unchanged. This only happens in syncRepeaterFields.


Evidence

Saved row vs what was ticked

Edit form, spouse repeater, post loaded from ACF. data-brf-repeater-data on the repeater:

{
  "s-p_person": [9242],
  "s-p_type": "Spouse",
  "s-p_current": ["Previous"]
}

The person select (value 9242) showed the correct person. The Current / Previous radios showed Current checked.

The first radio in that group is Current. The saved value is Previous. The loader ticked the first radio because the value is non-empty.

The same pass leaves Biological ticked on a Step / Family group when Biological is the first radio and the saved value is any non-empty string or object.

Code

bricksforge/assets/js/bricksforge_elements.js (4.0.0), inside syncRepeaterFields:

let input = field.querySelector("input, textarea, select");
if (input.type === "checkbox" || input.type === "radio") {
  input.checked = !!savedValue;
} else {
  input.value = savedValue;
}

querySelector returns the first input. !!savedValue is true for "Previous", ["Previous"], and { "value": "biological", "label": "Biological" }. The input’s own value is never compared.

Call path: handleRepeaters → populateRepeaterData → syncRepeaterFields.


Cause

syncRepeaterFields treats every radio and checkbox as a single boolean. It does not:

  • read input.value
  • walk the rest of the radio group
  • unwrap a one-item array (["Previous"], which is what an ACF checkbox stores)
  • unwrap an ACF select returned as { value, label }

So the first choice in the group is ticked whenever the saved value is truthy.


Suggested fix

Match the saved value to each radio or checkbox in the group:

const inputs = field.querySelectorAll("input[type=radio], input[type=checkbox]");
if (inputs.length) {
  let raw = savedValue;
  if (raw && typeof raw === "object" && !Array.isArray(raw) && raw.value != null) {
    raw = raw.value;
  }
  const wanted = (Array.isArray(raw) ? raw : [raw]).map(v => String(v).toLowerCase());
  inputs.forEach(input => {
    input.checked = wanted.includes(String(input.value).toLowerCase());
  });
  return;
}

Leave the existing element.value = savedValue path for text, textarea, and select.


What this is not

  • Not the wrong value in ACF. The repeater data attribute contains the saved choice.
  • Not fixed by changing the ACF return format of the choice field. A string "Previous" is still truthy, so the first radio is still ticked.
  • Not caused by theme or form CSS. The wrong radio has the checked property set.

Happy to test a patch on a staging install.

Hi Pete,

thanks for the precise report, I checked the code and can confirm it. syncRepeaterFields takes the first input inside the control and sets checked depending on whether the saved value is truthy, so for radios and checkboxes the first choice is always ticked. The input’s own value is never compared, exactly as you describe.

I’ll fix it along the lines of your suggestion: the saved value (plain string, one-item array or ACF’s value/label object) gets matched against every radio and checkbox in the group, and only the matching ones are checked. Text, textarea and select keep the current value assignment. I’ll update this thread once it has shipped.

Best
Daniele