Skip to content

Commit 5583bc9

Browse files
committed
bug #278 [LiveComponent] Avoid polling over renders (weaverryan)
This PR was squashed before being merged into the 2.x branch. Discussion ---------- [LiveComponent] Avoid polling over renders | Q | A | ------------- | --- | Bug fix? | yes | New feature? | no | Tickets | #102 Bug D | License | MIT The situation: A) User clicks a button that triggers an Ajax request B) While that is processing, polling initiates a new request C) Original Ajax request from (A) finishes... but since a newer request has already been initiated (from B), its result is never rendered (only the "latest" Ajax request's response is rendered if multiple happen on top of each other). This fixes that situation by skipping a polling request if there is already a request active. Commits ------- 6076c02 [LiveComponent] Avoid polling over renders
2 parents a219df2 + 6076c02 commit 5583bc9

File tree

2 files changed

+28
-11
lines changed

2 files changed

+28
-11
lines changed

src/LiveComponent/assets/dist/live_controller.js

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1107,7 +1107,7 @@ class default_1 extends Controller {
11071107
});
11081108
}
11091109
$render() {
1110-
this._makeRequest(null);
1110+
this._makeRequest(null, {});
11111111
}
11121112
_getValueFromElement(element) {
11131113
return element.dataset.value || element.value;
@@ -1121,17 +1121,18 @@ class default_1 extends Controller {
11211121
const clonedElement = cloneHTMLElement(element);
11221122
throw new Error(`The update() method could not be called for "${clonedElement.outerHTML}": the element must either have a "data-model" or "name" attribute set to the model name.`);
11231123
}
1124+
let finalValue = value;
11241125
if (/\[]$/.test(model)) {
11251126
const { currentLevelData, finalKey } = parseDeepData(this.dataValue, normalizeModelName(model));
11261127
const currentValue = currentLevelData[finalKey];
1127-
value = updateArrayDataFromChangedElement(element, value, currentValue);
1128+
finalValue = updateArrayDataFromChangedElement(element, value, currentValue);
11281129
}
11291130
else if (element instanceof HTMLInputElement
11301131
&& element.type === 'checkbox'
11311132
&& !element.checked) {
1132-
value = null;
1133+
finalValue = null;
11331134
}
1134-
this.$updateModel(model, value, shouldRender, element.hasAttribute('name') ? element.getAttribute('name') : null, {});
1135+
this.$updateModel(model, finalValue, shouldRender, element.hasAttribute('name') ? element.getAttribute('name') : null, {});
11351136
}
11361137
$updateModel(model, value, shouldRender = true, extraModelName = null, options = {}) {
11371138
const directives = parseDirectives(model);
@@ -1427,10 +1428,13 @@ class default_1 extends Controller {
14271428
}
14281429
else {
14291430
callback = () => {
1430-
this._makeRequest(actionName);
1431+
this._makeRequest(actionName, {});
14311432
};
14321433
}
14331434
const timer = setInterval(() => {
1435+
if (this.renderPromiseStack.countActivePromises() > 0) {
1436+
return;
1437+
}
14341438
callback();
14351439
}, duration);
14361440
this.pollingIntervals.push(timer);
@@ -1539,6 +1543,9 @@ class PromiseStack {
15391543
findPromiseIndex(promise) {
15401544
return this.stack.findIndex((item) => item === promise);
15411545
}
1546+
countActivePromises() {
1547+
return this.stack.length;
1548+
}
15421549
}
15431550
const parseLoadingAction = function (action, isLoading) {
15441551
switch (action) {

src/LiveComponent/assets/src/live_controller.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ export default class extends Controller {
184184
}
185185

186186
$render() {
187-
this._makeRequest(null);
187+
this._makeRequest(null, {});
188188
}
189189

190190
_getValueFromElement(element: HTMLElement|SVGElement) {
@@ -206,22 +206,23 @@ export default class extends Controller {
206206
// HTML form elements with name ending with [] require array as data
207207
// we need to handle addition and removal of values from it to send
208208
// back only required data
209+
let finalValue : string|null|string[] = value
209210
if (/\[]$/.test(model)) {
210211
// Get current value from data
211212
const { currentLevelData, finalKey } = parseDeepData(this.dataValue, normalizeModelName(model))
212213
const currentValue = currentLevelData[finalKey];
213214

214-
value = updateArrayDataFromChangedElement(element, value, currentValue);
215+
finalValue = updateArrayDataFromChangedElement(element, value, currentValue);
215216
} else if (
216217
element instanceof HTMLInputElement
217218
&& element.type === 'checkbox'
218219
&& !element.checked
219220
) {
220221
// Unchecked checkboxes in a single value scenarios should map to `null`
221-
value = null;
222+
finalValue = null;
222223
}
223224

224-
this.$updateModel(model, value, shouldRender, element.hasAttribute('name') ? element.getAttribute('name') : null, {});
225+
this.$updateModel(model, finalValue, shouldRender, element.hasAttribute('name') ? element.getAttribute('name') : null, {});
225226
}
226227

227228
/**
@@ -309,7 +310,7 @@ export default class extends Controller {
309310
}
310311
}
311312

312-
_makeRequest(action: string|null, args: Record<string,unknown>) {
313+
_makeRequest(action: string|null, args: Record<string, string>) {
313314
const splitUrl = this.urlValue.split('?');
314315
let [url] = splitUrl
315316
const [, queryString] = splitUrl;
@@ -659,11 +660,16 @@ export default class extends Controller {
659660
}
660661
} else {
661662
callback = () => {
662-
this._makeRequest(actionName);
663+
this._makeRequest(actionName, {});
663664
}
664665
}
665666

666667
const timer = setInterval(() => {
668+
// if there is already an active render promise, skip the poll
669+
if (this.renderPromiseStack.countActivePromises() > 0) {
670+
return;
671+
}
672+
667673
callback();
668674
}, duration);
669675
this.pollingIntervals.push(timer);
@@ -820,6 +826,10 @@ class PromiseStack {
820826
findPromiseIndex(promise: Promise<any>) {
821827
return this.stack.findIndex((item) => item === promise);
822828
}
829+
830+
countActivePromises(): number {
831+
return this.stack.length;
832+
}
823833
}
824834

825835
const parseLoadingAction = function(action: string, isLoading: boolean) {

0 commit comments

Comments
 (0)