Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

reactive query parameters with built-in useAsyncData #238

Open
alexanderivn opened this issue Feb 2, 2024 · 12 comments
Open

reactive query parameters with built-in useAsyncData #238

alexanderivn opened this issue Feb 2, 2024 · 12 comments
Assignees
Labels
bug Something isn't working help wanted Extra attention is needed
Milestone

Comments

@alexanderivn
Copy link

alexanderivn commented Feb 2, 2024

Hello, i have question regarding of watch and search inside the module, im trying to implement search function and filtering function, but it seems doesnt work, the api call from search value is always empty, this is working fine if i use the nuxt-directus plugin (https://github.com/Intevel/nuxt-directus).

here is my composables.

export default async function useGetAllServices(): Promise<{
  refresh: unknown
  siteUrl: unknown
  pending: unknown
  error: unknown
  search: unknown
  services: Services[]
}> {
  const { readItems } = useDirectusItems()
  const { client: siteUrl } = useDirectusFiles()
  const search = ref('')

  const {
    data: services,
    error,
    pending,
    refresh,
  } = await readItems('services', {
    key: 'service',
    params: {
      watch: [search],
      lazy: true,
      immediate: true,
    },
    query: {
      fields: [
        'title',
        'slug',
        'icon',
        'small_description',
        'content',
        { icon: ['id', 'filename_disk', 'filename_download'] },
      ],
      search: search.value,
      filter: {
        // @ts-ignore
        status: {
          _eq: 'published',
        },
      },
    },
  })
  return { services, error, pending, refresh, siteUrl, search }
}

And this is my vue component

<script lang="ts" setup>
const { services, error, refresh, pending, siteUrl, search } = await useGetAllServices()
</script>

<template>
<section class="container mx-auto">
     <input v-model="search" class="my-8 w-full border py-4" type="text" />
     .......
</section>
</template>

but the search response value always empty.

firefox_yIZVHvwbgi

Am i doing something wrong here? thank you!

@alexanderivn alexanderivn added the question Further information is requested label Feb 2, 2024
@Sandros94
Copy link
Collaborator

Sandros94 commented Feb 2, 2024

here is my composables.

// [...]
const { client: siteUrl } = useDirectusFiles()
// [...]

First thing first, just to make sure, this isn't the correct way to get your directus siteUrl. That client is used internally by the module or for more advanced use cases using the SDK directly (you can read about it in the official Directus's Docs):

If you configured your directus.url correctly in nuxt.config.ts, you can access it via:

const { url } = useRuntimeConfig().publlic.directus

or if you would like to change the url only for that particular composable you could do this:

const { readItems } = useDirectusItems({ options: { baseURL: 'https://my-different-directus-instance.com' } })

But more importantly, could you share a copy of the error you are getting in the console, or better of, the message from the error export from your composable?

@alexanderivn
Copy link
Author

alexanderivn commented Feb 2, 2024

Thank you for replying.

ah. i use this to get the image
const { client: siteUrl } = useDirectusFiles()

vue component
:src="`${siteUrl.url}assets/${service.icon?.filename_disk}?key=webp-icon`".

for my issue, there is no error in the console, i can get the data and show it to the front end without a problem, but when i try to search and use watch, its just return an empty search value. like shown below

image

Thank you.

@Sandros94
Copy link
Collaborator

vue component
:src="`${siteUrl.url}assets/${service.icon?.filename_disk}?key=webp-icon`".

oh, that is an interesting use and never realized that. Perhaps you could export the url from your useDirectusItems() that you are already using, instead of calling yet another composable+client. Better of, exposing only the url itself:

const { readItems, client: { url } } = useDirectusItems()

On the main topic, sorry for the distraction:

I'm testing this right now, and other than noticing an error with how I implemented useAsyncData I'm also able to reproduce the issue. It might not be that simple to solve.

@Sandros94
Copy link
Collaborator

Sandros94 commented Feb 3, 2024

I'm going to tag the boys here: @Intevel @casualmatt

This is my simple reproduction in our playground:

~/playground/composables/test.ts

import type { Ref } from 'vue'
import { readItems as readItemsSDK } from '@directus/sdk'
import { useAsyncData, useDirectusItems } from '#imports'
import type { Schema } from '../types'

export function myComposable () {
  const { client } = useDirectusItems<Schema>()

  async function readItemsTest (fieldParam: Ref<string>, searchParam: Ref<string>) {
    return await useAsyncData(
      () => client.request(readItemsSDK('posts', {
        fields: [fieldParam.value],
        search: searchParam.value
      })), {
        immediate: false,
        watch: [fieldParam, searchParam]
      }
    )
  }

  return {
    readItemsTest
  }
}

~/playground/pages/test.vue

<template>
  <div>
    <input v-model="fieldParam" type="text" placeholder="Field Param">
    <input v-model="searchParam" type="text" placeholder="Search posts">
    <button @click="refresh(); refreshToo(); refreshThree()">
      Refresh
    </button>
    <pre v-if="notWorking && notWorking.length > 0">
      useAsyncData
      {{ notWorking }}
    </pre>
    <pre v-if="working && working.length > 0">
      SDK
      {{ working }}
    </pre>
    <pre v-if="workingToo && workingToo.length > 0">
      myComposable
      {{ workingToo }}
    </pre>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { readItems as readItemsSDK } from '@directus/sdk'
import { useAsyncData, useDirectusItems } from '#imports'
import { myComposable } from '../composables/test'
import type { Schema } from '../types'

const { readItems, client } = useDirectusItems<Schema>()

const fieldParam = ref<string>('id')
const searchParam = ref<string>('')

const { data: notWorking, refresh } = await readItems('posts', {
  query: {
    fields: [fieldParam.value],
    search: searchParam.value
  },
  params: {
    immediate: false,
    watch: [fieldParam, searchParam]
  }
})

const { data: working, refresh: refreshToo } = await useAsyncData(
  () => client.request(readItemsSDK('posts', {
    fields: [fieldParam.value],
    search: searchParam.value
  })), {
    immediate: false,
    watch: [fieldParam, searchParam]
  }
)

const { readItemsTest } = myComposable()

const { data: workingToo, refresh: refreshThree } =
  await readItemsTest(fieldParam, searchParam)
</script>

What happens

In short: something strange is happening with useAsyncData when it is part of a module. From what it seems it bakes the query parameters into the sdk function, as shown here:

Recording.2024-02-03.033707.mp4

Should we ask the bigger boys?

@Sandros94
Copy link
Collaborator

Sandros94 commented Feb 3, 2024

Oh, now that I think about it, this also explains a few issue I had months ago about collections variables and I thought were solved by placing them into refs

@Sandros94
Copy link
Collaborator

in the meanwhile @alexanderivn you could create your local composable like this:

import { readItems } from '@directus/sdk'

export default async function useGetAllServices () {
  const { client } = useDirectusRest()
  const search = ref('')

  return {
    url: client.url,
    search,
    services: await useAsyncData('services', () =>
      client.request(readItems('services', {
        fields: [
          'title',
          'slug',
          'icon',
          'small_description',
          'content',
          { icon: ['id', 'filename_disk', 'filename_download'] },
        ],
        search: search.value,
        filter: {
          status: {
            _eq: 'published',
          },
        },
      }), {
        watch: [search],
        lazy: true,
        immediate: true,
      }))
  }
}

@alexanderivn
Copy link
Author

alexanderivn commented Feb 5, 2024

in the meanwhile @alexanderivn you could create your local composable like this:

Thank you, Sorry for the late reply, yes will try this..

Thank you so much!

@Sandros94 Sandros94 added bug Something isn't working help wanted Extra attention is needed and removed question Further information is requested labels Feb 5, 2024
@Sandros94 Sandros94 self-assigned this Feb 5, 2024
@Sandros94
Copy link
Collaborator

Sandros94 commented Feb 5, 2024

I'm going to treat this issue also as a backlog for my experimentations on this topic.

I was able to reproduce the issue even in a local composable (inside the playground), more precisely without even using the SDK. This means that the issue is not related to the SDK nor how the module is built, but almost solely to how I use useAsyncData.

EDIT: and I also triple confirm that the key generation isn't the issue, since this happens with this too:

  async function testReadItems <
  Collection extends RegularCollections<Schema>,
  TQuery extends Query<Schema, CollectionType<Schema, Collection>>
  > (
    collection: MaybeRef<Collection>,
    params?: DirectusItemsOptionsAsyncData<TQuery>
  ) {
    return await useAsyncData(
      () => Promise.resolve([{
        collection,
        query: params?.query,
        params: params?.params
      }]),
      params?.params
    )
  }

@Sandros94
Copy link
Collaborator

Sandros94 commented Feb 5, 2024

I might have understood what is going on.

In short Vue loses the reactivity tracking for things inside non-reactive parameters like params.query.

I've noticed this while fixing the reactivity for the collection names in 23f1bef, that was a workaround that I though I could use while first rewriting this module, but didn't fully understand why it was needed.

If my understanding on Vue's reactivity system is correct: it is like putting an outter shell to the value you want to make reactive and it acts like a fingerprint. Once Vue starts, it keeps a record for only those fingerprints (I imagine for performance reasons), if the value changes the outter shell updates its fingerprint. Vue, on the next tick of operations, while going through the code it recognizes the reactive shell, checks its fingerprint with the one that already has and if it is different that re-elaborate all the functions that depends from that value.

If my understanding is correct, we might have an hard time using useAsyncData within our module...

UPDATE

One day I'll need to finish read the full Vue documentation, but yes, what I'm noticing is Vue working as intended... sources: 1, 2 and in general this could be helpful 3.

@Sandros94 Sandros94 changed the title How to implement search query and filtering reactive query parameters with built-in useAsyncData Feb 10, 2024
@Sandros94 Sandros94 added this to the v6.x milestone Feb 10, 2024
@Sandros94
Copy link
Collaborator

FYI

I'm delaying this till later we are in a stable 6.0.0 version, since it will require quite some time to fix.

@casualmatt
Copy link
Collaborator

Update:
UseAsyncData will not be implemented, and we change direction for the Nuxt-Directus composable.
See: #215 (comment)

@casualmatt casualmatt closed this as not planned Won't fix, can't repro, duplicate, stale Feb 15, 2024
@Sandros94
Copy link
Collaborator

I'm reopening this to mainly track reactivity of query parameters for the new implementation of useAsyncData.

More info can be found in the release discussed at #215 (comment).

@Sandros94 Sandros94 reopened this Feb 24, 2024
@Sandros94 Sandros94 modified the milestones: v6.x, v6.0.x Feb 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

3 participants