Skip to content

Commit cc13885

Browse files
1edweaverryan
authored andcommitted
[LiveComponent] Fix live component rendering when loaded from bfcache
1 parent f5bc06f commit cc13885

File tree

3 files changed

+43
-19
lines changed

3 files changed

+43
-19
lines changed

src/LiveComponent/assets/dist/live_controller.js

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1238,17 +1238,13 @@ class default_1 extends Controller {
12381238
this.isRerenderRequested = false;
12391239
this.requestDebounceTimeout = null;
12401240
this.pollingIntervals = [];
1241-
this.isWindowUnloaded = false;
1241+
this.isConnected = false;
12421242
this.originalDataJSON = '{}';
12431243
this.mutationObserver = null;
12441244
this.childComponentControllers = [];
12451245
this.pendingActionTriggerModelElement = null;
1246-
this.markAsWindowUnloaded = () => {
1247-
this.isWindowUnloaded = true;
1248-
};
12491246
}
12501247
initialize() {
1251-
this.markAsWindowUnloaded = this.markAsWindowUnloaded.bind(this);
12521248
this.handleUpdateModelEvent = this.handleUpdateModelEvent.bind(this);
12531249
this.handleInputEvent = this.handleInputEvent.bind(this);
12541250
this.handleChangeEvent = this.handleChangeEvent.bind(this);
@@ -1261,12 +1257,12 @@ class default_1 extends Controller {
12611257
this.synchronizeValueOfModelFields();
12621258
}
12631259
connect() {
1260+
this.isConnected = true;
12641261
this._onLoadingFinish();
12651262
if (!(this.element instanceof HTMLElement)) {
12661263
throw new Error('Invalid Element Type');
12671264
}
12681265
this._initiatePolling();
1269-
window.addEventListener('beforeunload', this.markAsWindowUnloaded);
12701266
this._startAttributesMutationObserver();
12711267
this.element.addEventListener('live:update-model', this.handleUpdateModelEvent);
12721268
this.element.addEventListener('input', this.handleInputEvent);
@@ -1277,7 +1273,6 @@ class default_1 extends Controller {
12771273
disconnect() {
12781274
this._stopAllPolling();
12791275
__classPrivateFieldGet(this, _instances, "m", _clearRequestDebounceTimeout).call(this);
1280-
window.removeEventListener('beforeunload', this.markAsWindowUnloaded);
12811276
this.element.removeEventListener('live:update-model', this.handleUpdateModelEvent);
12821277
this.element.removeEventListener('input', this.handleInputEvent);
12831278
this.element.removeEventListener('change', this.handleChangeEvent);
@@ -1287,6 +1282,7 @@ class default_1 extends Controller {
12871282
if (this.mutationObserver) {
12881283
this.mutationObserver.disconnect();
12891284
}
1285+
this.isConnected = false;
12901286
}
12911287
update(event) {
12921288
if (event.type === 'input' || event.type === 'change') {
@@ -1944,7 +1940,7 @@ _instances = new WeakSet(), _startPendingRequest = function _startPendingRequest
19441940
__classPrivateFieldGet(this, _instances, "m", _startPendingRequest).call(this);
19451941
});
19461942
}, _processRerender = function _processRerender(html, response) {
1947-
if (this.isWindowUnloaded) {
1943+
if (!this.isConnected) {
19481944
return;
19491945
}
19501946
if (response.headers.get('Location')) {

src/LiveComponent/assets/src/live_controller.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ export default class extends Controller implements LiveController {
7070

7171
pollingIntervals: NodeJS.Timer[] = [];
7272

73-
isWindowUnloaded = false;
73+
isConnected = false;
7474

7575
originalDataJSON = '{}';
7676

@@ -86,7 +86,6 @@ export default class extends Controller implements LiveController {
8686
pendingActionTriggerModelElement: HTMLElement|null = null;
8787

8888
initialize() {
89-
this.markAsWindowUnloaded = this.markAsWindowUnloaded.bind(this);
9089
this.handleUpdateModelEvent = this.handleUpdateModelEvent.bind(this);
9190
this.handleInputEvent = this.handleInputEvent.bind(this);
9291
this.handleChangeEvent = this.handleChangeEvent.bind(this);
@@ -100,6 +99,7 @@ export default class extends Controller implements LiveController {
10099
}
101100

102101
connect() {
102+
this.isConnected = true;
103103
// hide "loading" elements to begin with
104104
// This is done with CSS, but only for the most basic cases
105105
this._onLoadingFinish();
@@ -111,7 +111,6 @@ export default class extends Controller implements LiveController {
111111

112112
this._initiatePolling();
113113

114-
window.addEventListener('beforeunload', this.markAsWindowUnloaded);
115114
this._startAttributesMutationObserver();
116115
this.element.addEventListener('live:update-model', this.handleUpdateModelEvent);
117116
this.element.addEventListener('input', this.handleInputEvent);
@@ -125,7 +124,6 @@ export default class extends Controller implements LiveController {
125124
this._stopAllPolling();
126125
this.#clearRequestDebounceTimeout();
127126

128-
window.removeEventListener('beforeunload', this.markAsWindowUnloaded);
129127
this.element.removeEventListener('live:update-model', this.handleUpdateModelEvent);
130128
this.element.removeEventListener('input', this.handleInputEvent);
131129
this.element.removeEventListener('change', this.handleChangeEvent);
@@ -137,6 +135,8 @@ export default class extends Controller implements LiveController {
137135
if (this.mutationObserver) {
138136
this.mutationObserver.disconnect();
139137
}
138+
139+
this.isConnected = false;
140140
}
141141

142142
/**
@@ -493,7 +493,7 @@ export default class extends Controller implements LiveController {
493493
*/
494494
#processRerender(html: string, response: Response) {
495495
// check if the page is navigating away
496-
if (this.isWindowUnloaded) {
496+
if (!this.isConnected) {
497497
return;
498498
}
499499

@@ -795,10 +795,6 @@ export default class extends Controller implements LiveController {
795795
this._exposeOriginalData();
796796
}
797797

798-
markAsWindowUnloaded = () => {
799-
this.isWindowUnloaded = true;
800-
};
801-
802798
handleConnectedControllerEvent(event: any) {
803799
if (event.target === this.element) {
804800
return;

src/LiveComponent/assets/test/controller/render.test.ts

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,8 +231,8 @@ describe('LiveController rendering Tests', () => {
231231
.init();
232232

233233
test.controller.$render();
234-
// imitate navigating away
235-
fireEvent(window, createEvent('beforeunload', window));
234+
// trigger disconnect
235+
test.element.removeAttribute('data-controller')
236236

237237
// wait for the fetch to finish
238238
await fetchMock.flush();
@@ -241,6 +241,38 @@ describe('LiveController rendering Tests', () => {
241241
expect(test.element).not.toHaveTextContent('Hello');
242242
});
243243

244+
it('renders if the page is navigating away and back', async () => {
245+
const test = await createTest({greeting: 'aloha!'}, (data: any) => `
246+
<div ${initComponent(data)}>${data.greeting}</div>
247+
`);
248+
249+
test.expectsAjaxCall('get')
250+
.expectSentData(test.initialData)
251+
.serverWillChangeData((data) => {
252+
data.greeting = 'Hello';
253+
})
254+
.delayResponse(100)
255+
.init();
256+
257+
test.controller.$render();
258+
259+
// trigger controller disconnect
260+
test.element.removeAttribute('data-controller')
261+
// wait for the fetch to finish
262+
await fetchMock.flush();
263+
264+
expect(test.element).toHaveTextContent('aloha!')
265+
266+
// trigger connect
267+
test.element.setAttribute('data-controller', 'live')
268+
test.controller.$render();
269+
// wait for the fetch to finish
270+
await fetchMock.flush();
271+
272+
// the re-render should have happened
273+
expect(test.element).toHaveTextContent('Hello');
274+
});
275+
244276
it('waits for the previous request to finish & batches changes', async () => {
245277
const test = await createTest({ title: 'greetings', contents: '' }, (data: any) => `
246278
<div ${initComponent(data, { debounce: 1 })}>

0 commit comments

Comments
 (0)