I have two components which display different information on the same data object. The object is placed in the store. Both components subscribe store actions to detect changes and re-render.
Component A is a child of Component B.
Component A
// used to track changes; refs.example is used to influence rendered DOM elements
const refs: {
example?: Exercise,
} = reactive({
example: undefined,
})
// subscribe to changes of selected
store.subscribeAction({
after: (action) => {
if (!subscribedActions.includes(action.type)) return
// selected was altered, set example to trigger re-render
refs.example = store.getters['selected'] as Example
},
})
Component B
// Analog to component A
When selected
is altered by an action, both refs.example
are set and the components re-render. Component A re-renders before B.
Yet: Once B re-renders, Component A is rendered again as if its refs.example
was undefined
(if the setting of refs.example
in Component B is delayed by setTimeout
, Component A renders normally until Component B's refs.example
is set). Though, if Component A is delayed until B has re-rendered, Component A never renders normally.
If B renders after A, there is a short a time when both components render normally. Component A is rendered "as undefined" at the end of this part of
Vue's source code
function flushJobs(seen) {
isFlushPending = false;
isFlushing = true;
if ((process.env.NODE_ENV !== 'production')) {
seen = seen || new Map();
}
flushPreFlushCbs(seen);
// Sort queue before flush.
// This ensures that:
// 1. Components are updated from parent to child. (because parent is always
// created before the child so its render effect will have smaller
// priority number)
// 2. If a component is unmounted during a parent component's update,
// its update can be skipped.
queue.sort((a, b) => getId(a) - getId(b));
// conditional usage of checkRecursiveUpdate must be determined out of
// try ... catch block since Rollup by default de-optimizes treeshaking
// inside try-catch. This can leave all warning code unshaked. Although
// they would get eventually shaken by a minifier like terser, some minifiers
// would fail to do that (e.g. https://github.com/evanw/esbuild/issues/1610)
const check = (process.env.NODE_ENV !== 'production')
? (job) => checkRecursiveUpdates(seen, job)
: NOOP;
try {
for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {
const job = queue[flushIndex];
if (job && job.active !== false) {
if ((process.env.NODE_ENV !== 'production') && check(job)) {
continue;
}
// console.log(`running:`, job.id)
// COMPONENT A IS RENDERED AS UNDEFINED HERE
callWithErrorHandling(job, null, 14 /* SCHEDULER */);
}
}
}
...
Any ideas on the issue and its solution?
While I do not know why this problem occured, I found a way to react to changes of a single object in store in two different components: Make use of store getters and computed()
:
Component A
const refs: {
example?: Exercise,
} = reactive({
example: computed(() => store.getters['examples/selected'] as Exercise),
})
The same code could be used in Component B. Alternatively, since B is a child of A, A could pass the refs
down to B.