Skip to content

Commit 34c6ad9

Browse files
authored
fix: detect and apply changes triggered before or during initialization (#377)
1 parent 011a71d commit 34c6ad9

File tree

3 files changed

+101
-1
lines changed

3 files changed

+101
-1
lines changed

src/client/update.js

+7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22
let batchId = null
33

44
export function triggerUpdate(vm, hookName) {
5+
// if an update was triggered during initialization or when an update was triggered by the
6+
// metaInfo watcher, set initialized to null
7+
// then we keep falsy value but know we need to run a triggerUpdate after initialization
8+
if (!vm.$root._vueMeta.initialized && (vm.$root._vueMeta.initializing || hookName === 'watcher')) {
9+
vm.$root._vueMeta.initialized = null
10+
}
11+
512
if (vm.$root._vueMeta.initialized && !vm.$root._vueMeta.paused) {
613
// batch potential DOM updates to prevent extraneous re-rendering
714
batchUpdate(() => vm.$meta().refresh())

src/shared/mixin.js

+18-1
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,29 @@ export default function createMixin(Vue, options) {
7272
this.$root._vueMeta.initialized = this.$isServer
7373

7474
if (!this.$root._vueMeta.initialized) {
75+
// we use the mounted hook here as on page load
7576
ensuredPush(this.$options, 'mounted', () => {
7677
if (!this.$root._vueMeta.initialized) {
78+
// used in triggerUpdate to check if a change was triggered
79+
// during initialization
80+
this.$root._vueMeta.initializing = true
81+
7782
// refresh meta in nextTick so all child components have loaded
7883
this.$nextTick(function () {
79-
const { metaInfo } = this.$root.$meta().refresh()
84+
const { tags, metaInfo } = this.$root.$meta().refresh()
85+
86+
// After ssr hydration (identifier by tags === false) check
87+
// if initialized was set to null in triggerUpdate. That'd mean
88+
// that during initilazation changes where triggered which need
89+
// to be applied OR a metaInfo watcher was triggered before the
90+
// current hook was called
91+
// (during initialization all changes are blocked)
92+
if (tags === false && this.$root._vueMeta.initialized === null) {
93+
this.$nextTick(() => triggerUpdate(this, 'initializing'))
94+
}
95+
8096
this.$root._vueMeta.initialized = true
97+
delete this.$root._vueMeta.initializing
8198

8299
// add the navigation guards if they havent been added yet
83100
// they are needed for the afterNavigation callback

test/unit/components.test.js

+76
Original file line numberDiff line numberDiff line change
@@ -197,4 +197,80 @@ describe('client', () => {
197197
guards.after()
198198
expect(afterNavigation).toHaveBeenCalled()
199199
})
200+
201+
test('changes before hydration initialization trigger an update', async () => {
202+
html.setAttribute(defaultOptions.ssrAttribute, 'true')
203+
204+
// this component uses a computed prop to simulate a non-synchronous
205+
// metaInfo update like you would have with a Vuex mutation
206+
const component = Vue.component('test-component', {
207+
data() {
208+
return {
209+
hiddenTheme: 'light'
210+
}
211+
},
212+
computed: {
213+
theme() {
214+
return this.hiddenTheme
215+
}
216+
},
217+
beforeMount() {
218+
this.hiddenTheme = 'dark'
219+
},
220+
render: h => h('div'),
221+
metaInfo() {
222+
return {
223+
htmlAttrs: {
224+
theme: this.theme
225+
}
226+
}
227+
}
228+
})
229+
230+
const wrapper = mount(component, { localVue: Vue })
231+
expect(html.getAttribute('theme')).not.toBe('dark')
232+
233+
await vmTick(wrapper.vm)
234+
jest.runAllTimers()
235+
236+
expect(html.getAttribute('theme')).toBe('dark')
237+
html.removeAttribute('theme')
238+
})
239+
240+
test('changes during hydration initialization trigger an update', async () => {
241+
html.setAttribute(defaultOptions.ssrAttribute, 'true')
242+
243+
const component = Vue.component('test-component', {
244+
data() {
245+
return {
246+
hiddenTheme: 'light'
247+
}
248+
},
249+
computed: {
250+
theme() {
251+
return this.hiddenTheme
252+
}
253+
},
254+
mounted() {
255+
this.hiddenTheme = 'dark'
256+
},
257+
render: h => h('div'),
258+
metaInfo() {
259+
return {
260+
htmlAttrs: {
261+
theme: this.theme
262+
}
263+
}
264+
}
265+
})
266+
267+
const wrapper = mount(component, { localVue: Vue })
268+
expect(html.getAttribute('theme')).not.toBe('dark')
269+
270+
await vmTick(wrapper.vm)
271+
jest.runAllTimers()
272+
273+
expect(html.getAttribute('theme')).toBe('dark')
274+
html.removeAttribute('theme')
275+
})
200276
})

0 commit comments

Comments
 (0)