Skip to content

Commit a008950

Browse files
committed
[withBreakpointObserver] Fix the order of mount/destroy triggers
When components are working with the same markup, it is necessary to have a predicable order of `destroyed` and `mounted` hooks call. The mount hook is delayed to the next loop with a `setTimeout(cb, 0)` call.
1 parent abd36f4 commit a008950

File tree

2 files changed

+83
-14
lines changed

2 files changed

+83
-14
lines changed

src/decorators/withBreakpointObserver.js

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,29 @@ function testConflictingBreakpointConfiguration(instance) {
4747
}
4848
}
4949

50+
/**
51+
* Add the current instance to the resize service.
52+
* @param {String} key The key for the resize service callback.
53+
* @param {Base} instance The instance to observe.
54+
*/
55+
function addToResize(key, instance) {
56+
testConflictingBreakpointConfiguration(instance);
57+
const { add, has } = useResize();
58+
59+
if (!has(key)) {
60+
add(key, function onResize({ breakpoint }) {
61+
const action = testBreakpoints(instance, breakpoint);
62+
63+
// Always destroy before mounting
64+
if (action === '$destroy' && instance.$isMounted) {
65+
instance[action]();
66+
} else if (action === '$mount' && !instance.$isMounted) {
67+
setTimeout(() => instance[action](), 0);
68+
}
69+
});
70+
}
71+
}
72+
5073
/**
5174
* BreakpointObserver class.
5275
*/
@@ -83,14 +106,7 @@ export default (BaseClass) =>
83106
return;
84107
}
85108

86-
testConflictingBreakpointConfiguration(this);
87-
88-
if (!has(key)) {
89-
add(key, ({ breakpoint }) => {
90-
const action = testBreakpoints(this, breakpoint);
91-
this[action]();
92-
});
93-
}
109+
addToResize(key, this);
94110
}
95111
});
96112
mutationObserver.observe(this.$el, { attributes: true });
@@ -100,12 +116,7 @@ export default (BaseClass) =>
100116
return this;
101117
}
102118

103-
testConflictingBreakpointConfiguration(this);
104-
add(key, ({ breakpoint }) => {
105-
const action = testBreakpoints(this, breakpoint);
106-
this[action]();
107-
});
108-
119+
addToResize(key, this);
109120
return this;
110121
}
111122

tests/decorators/withBreakpointObserver.spec.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,64 @@ describe('The withBreakpointObserver decorator', () => {
114114
}).toThrow(/Incorrect configuration/);
115115
});
116116

117+
it('should destroy components before mounting the others', async () => {
118+
await resizeWindow({ width: 800 });
119+
const fn = jest.fn();
120+
121+
class Mobile extends withBreakpointObserver(Base) {
122+
get config() {
123+
return { name: 'Mobile', inactiveBreakpoints: 'm' };
124+
}
125+
126+
mounted() {
127+
fn('Mobile', 'mounted')
128+
}
129+
130+
destroyed() {
131+
fn('Mobile', 'destroyed');
132+
}
133+
}
134+
135+
class Desktop extends withBreakpointObserver(Base) {
136+
get config() {
137+
return { name: 'Desktop', activeBreakpoints: 'm' };
138+
}
139+
140+
mounted() {
141+
fn('Desktop', 'mounted')
142+
}
143+
144+
destroyed() {
145+
fn('Desktop', 'destroyed');
146+
}
147+
}
148+
149+
class App extends Base {
150+
get config() {
151+
return {
152+
name: 'App',
153+
components: { Mobile, Desktop }
154+
}
155+
}
156+
}
157+
158+
document.body.innerHTML = `
159+
<div data-breakpoint>
160+
<div data-component="Mobile"></div>
161+
<div data-component="Desktop"></div>
162+
</div>
163+
`;
164+
165+
const app = new App(document.body);
166+
expect(fn).toHaveBeenCalledWith('Desktop', 'mounted');
167+
await resizeWindow({ width: 400 });
168+
expect(fn).toHaveBeenNthCalledWith(2, 'Desktop', 'destroyed');
169+
expect(fn).toHaveBeenNthCalledWith(3, 'Mobile', 'mounted');
170+
await resizeWindow({ width: 800 });
171+
expect(fn).toHaveBeenNthCalledWith(4, 'Mobile', 'destroyed');
172+
expect(fn).toHaveBeenNthCalledWith(5, 'Desktop', 'mounted');
173+
});
174+
117175
it('should throw when breakpoints are not availabe', () => {
118176
expect(() => {
119177
document.body.innerHTML = '<div></div>';

0 commit comments

Comments
 (0)