Skip to content

[🐞] Computed unmount & component unmount race condition issue #5203

Open
@Kasheftin

Description

@Kasheftin

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 or npm 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions