Bug report — Pro Forms Date field inside an ACF-populated Repeater renders two visible inputs
Submitted by: HHH Consulting
Component: Pro Forms (nestable) — Repeater + Date field
Severity: Medium — the control still works, but a second empty date input is shown under it and the console throws
Type: Front-end JS / Flatpickr re-init
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 extra input is not present on Date fields outside a Repeater, and not present on Date fields inside a Repeater that has no ACF rows to populate.
Summary
A Pro Forms Date field is a single Bricks element. Flatpickr is configured with altInput: true, which is correct: one hidden original input plus one visible calendar input.
When that Date field sits inside a Repeater and the Repeater is populated from ACF (dataSource: acf with existing rows), handleDatePickers() runs again after populateRepeaterData. It initialises Flatpickr a second time without destroying the previous instance, so a second visible altInput is left in the DOM.
The first visible input opens the picker. The second is empty and does nothing when clicked.
A console error is thrown on the same pass, because the second loop also matches leftover .flatpickr-input nodes that have no data-bricks-datepicker-options.
Steps to reproduce
A relationship / post-object sibling field is not required. Any ACF repeater with a date subfield is enough.
- In ACF, create a Repeater on a post type. Give it one Date Picker subfield (name e.g.
moved_in). - Save at least one post with two repeater rows and dates filled in.
- In Bricks, add a Pro Form.
- Add a Repeater field:
- Custom ID matching the ACF repeater name (or
fieldNameset to that name) dataSource= ACFpostId= the post from step 2
- Custom ID matching the ACF repeater name (or
- Inside the Repeater, add one Date field whose Custom ID matches the date subfield name. Leave Flatpickr defaults (
altInputon). - View the page on the front end for that post.
Expected: each Date field shows one visible calendar input.
Actual: each populated row shows two visible calendar inputs stacked. Only the first opens the picker. The second is empty and inert.
Repeat the same form with no ACF rows (or a Date field outside the Repeater): only one visible input. That is the control case.
Evidence
Rendered markup — Date inside the populated Repeater
One Bricks Date element (data-element-id="nqtllc", custom ID moved_in). After ACF populate it contains three inputs: the hidden original, a working alt input, and a leftover empty alt input.
<div class="brxe-nqtllc brxe-brf-pro-forms-field-date pro-forms-builder-field form-group"
data-element-id="nqtllc"
data-custom-id="moved_in">
<label for="form-field-f67d88-0">Date</label>
<input type="hidden"
id="form-field-f67d88-0"
name="brfr[addresss][0][moved_in]"
class="flatpickr flatpickr-input"
data-bricks-datepicker-options="{...,"altInput":true,"altFormat":"d\/m\/Y","dateFormat":"Y-m-d",...}"
value="2005-06-01">
<input class="flatpickr form-control input" placeholder="" tabindex="0" type="text" readonly="readonly">
<input class="flatpickr form-control input" placeholder="" tabindex="0" type="text" readonly="readonly">
</div>
The first visible input holds the formatted date and is the live altInput. The second is empty, has no _flatpickr instance, and does not open a calendar.
The same extra empty input appears on Moved Out in the same rows.
Control — Date outside the Repeater (same form, same page)
<div class="brxe-iuwgqp brxe-brf-pro-forms-field-date pro-forms-builder-field form-group"
data-custom-id="date_of_birth">
<label for="form-field-343f23">Dob</label>
<input type="hidden" class="flatpickr flatpickr-input" name="form-field-date_of_birth" value="1972-11-07">
<input class="flatpickr form-control input" placeholder="" tabindex="0" type="text" readonly="readonly">
</div>
Hidden original + one visible alt input. Correct.
Date fields inside empty Repeaters on the same form (no ACF rows) also render correctly as 1 + 1.
Screenshot
See bricksforge-repeater-datepicker-duplicate.png in this folder. Each of Date (Moved In) and Date (Moved Out) has a filled input with a second empty input directly under it.
Console
TypeError: Cannot set properties of undefined (setting 'onChange')
at bricksforge_elements.js:1:92071
at NodeList.forEach (<anonymous>)
at BrfProForms.handleDatePickers (bricksforge_elements.js)
at BrfProForms.updateRepeaterItemIndexes (bricksforge_elements.js)
at BrfProForms.syncRepeaterFields (bricksforge_elements.js)
at BrfProForms.populateRepeaterData (bricksforge_elements.js)
at BrfProForms.handleRepeaters (bricksforge_elements.js)
Call path: handleRepeaters → populateRepeaterData → syncRepeaterFields → updateRepeaterItemIndexes → handleDatePickers.
Cause
In bricksforge/assets/js/bricksforge_elements.js (4.0.0), handleDatePickers() initialises Flatpickr twice in one call, and again whenever repeater indexes are updated:
- It runs
flatpickr()on.flatpickrnodes that havedata-bricks-datepicker-options. - On the front end it then queries all
.flatpickr-inputnodes and runsflatpickr()again.
The second pass does not:
- skip nodes that already have
element._flatpickr destroy()the existing instance (which would remove the previousaltInput)- skip nodes that have no
data-bricks-datepicker-options
altInput: true means each successful flatpickr() inserts a new visible input. A second init therefore leaves the old visible input in place.
The TypeError is the same function: leftover alt inputs also match .flatpickr-input but have no options object, so t is undefined when the code does t.onChange = ….
This is independent of other subfield types in the Repeater. Changing a sibling subfield from text to a post-object / relationship does not create the extra Date input. The extra nodes are Flatpickr altInput elements, created in JS after ACF populate.
Suggested fix
In handleDatePickers, only initialise original Date inputs, and only once:
// skip leftover alt inputs and already-live instances
if (!e.dataset.bricksDatepickerOptions) return;
if (e._flatpickr) {
e._flatpickr.destroy();
}
const t = JSON.parse(e.dataset.bricksDatepickerOptions);
t.onChange = /* existing handler */;
flatpickr(e, t);
destroy() removes the previous altInput before a re-init, which is what updateRepeaterItemIndexes needs after cloning/indexing rows.
Alternatively, keep a single init path (the first .flatpickr loop already does the job) and do not query .flatpickr-input again on the front end.
What this is not
- Not a duplicate Date element in the Bricks tree. There is one Date field per label.
- Not caused by a sibling Select / post-object / relationship field in the same Repeater row.
- Not caused by theme or form CSS. The second
<input class="flatpickr form-control input">is in the DOM.
Happy to test a patch on a staging install.