Description
Which component is affected?
Qwik Runtime
Describe the bug
This bug is about the incorrect order of evaluation/unmounting computed variables and unmounting the component itself.
Suppose, we have a dynamic route [...path]
which loads different components depending on the path
variable. Then, the global store is populated with different data depending on the path
.
The minimal demo app has the following store:
export enum DataType {
empty = 'empty',
first = 'first',
second = 'second'
}
export type FirstData = {
key1: { key1: string }
}
export type SecondData = {
key2: { key2: string }
}
export type Store = {
routepath: string
data: FirstData | SecondData | null
dataType: DataType
}
export const getInitialStore = (): Store => ({
routepath: '',
data: null,
dataType: DataType.empty
})
export const loadStore = async (routepath: string) => {
const store = getInitialStore()
store.routepath = routepath
const { data, dataType } = await loadData(store)
Object.assign(store, { data, dataType })
return store
}
const loadData = async (store: Store) => {
await timeout(500)
if (store.routepath === 'first') {
const data: FirstData = {
key1: {
key1: 'first'
}
}
return {
dataType: DataType.first,
data
}
} else if (store.routepath === 'second') {
const data: SecondData = {
key2: {
key2: 'second'
}
}
return {
dataType: DataType.second,
data
}
} else {
return {
dataType: DataType.empty,
data: null
}
}
}
Basically, loadStore
provides a connected pair of { data, dataType }
: if dataType
is DataType.first
, then data
has FirstData
type, else if dataType
is DataType.second
, then data
has SecondData
type. It's far from typescript best practice, but technically it's totally fine.
[...path]/index.tsx
file just loads different page component depending on the dataType
:
export default component$(() => {
const store = useContext(STORE)
const getPage = (dataType: DataType) => {
if (dataType === DataType.first) {
return (
<FirstPage />
)
}
if (dataType === DataType.second) {
return (
<SecondPage />
)
}
return (
<DefaultPage />
)
}
return (
<div>{getPage(store.dataType)}</div>
)
})
Finally, the page component heavily relies on the data
type: FirstPage
expects data
to have FirstData
type. Since <FirstPage>
is being shown only when dataType
is DataType.first
, it's natural to expect that store.data
has FirstData
type:
export default component$(() => {
const store = useContext(STORE)
const data = store.data as FirstData
const content = useComputed$(() => {
return data.key1.key1
})
return (
<div>
First Content: {content.value}
</div>
)
})
It seems to be a valid code. When the route changes, { data, dataType }
pair is substituted into the store in one tick. If dataType
changes, it should unmount the previous page and mount the new page.
However, there's something wrong with unmounting logic. When the page changes, it tries to evaluate useComputed$
for the old page using the new data before unmounting this old page. It should somehow know that the component is going to be unmounted and skip evaluating computeds.
Reproduction
https://github.com/Kasheftin/qwik-unmount-computed-bug
Steps to reproduce
npm run dev
ornpm run preview
- Click on "First Page" link
- Click on "Second Page" link
Cannot read properties of undefined
error appears
System Info
System:
OS: Linux 6.2 Ubuntu 22.04.2 LTS 22.04.2 LTS (Jammy Jellyfish)
CPU: (12) x64 Intel(R) Core(TM) i7-8700 CPU @ 3.20GHz
Memory: 20.19 GB / 31.30 GB
Container: Yes
Shell: 5.1.16 - /bin/bash
Binaries:
Node: 19.8.1 - ~/.nvm/versions/node/v19.8.1/bin/node
npm: 9.5.1 - ~/.nvm/versions/node/v19.8.1/bin/npm
Browsers:
Chrome: 115.0.5790.170
npmPackages:
@builder.io/qwik: ^1.2.12 => 1.2.12
@builder.io/qwik-city: ^1.2.12 => 1.2.12
undici: 5.22.1 => 5.22.1
vite: 4.4.7 => 4.4.7
Additional Information
No response