Search code examples
javascriptvue.jsquasar

Quasar QSelect is not opening when performing AJAX call


I have been trying to create a simple auto complete using Quasar's select but I'm not sure if this is a bug or if I'm doing something wrong.

Problem

Whenever I click the QSelect component, it doesn't show the dropdown where I can pick the options from.

video of the problem

As soon as I click on the QSelect component, I make a request to fetch a list of 50 tags, then I populate the tags to my QSelect but the dropdown doesn't show.

Code

import type { PropType } from "vue";
import { defineComponent, h, ref } from "vue";
import type { TagCodec } from "@/services/api/resources/tags/codec";
import { list } from "@/services/api/resources/tags/actions";
import { QSelect } from "quasar";

export const TagAutoComplete = defineComponent({
  name: "TagAutoComplete",
  props: {
    modelValue: { type: Array as PropType<TagCodec[]> },
  },
  emits: ["update:modelValue"],
  setup(props, context) {
    const loading = ref(false);

    const tags = ref<TagCodec[]>([]);

    // eslint-disable-next-line @typescript-eslint/ban-types
    const onFilterTest = (val: string, doneFn: (update: Function) => void) => {
      const parameters = val === "" ? {} : { title: val };

      doneFn(async () => {
        loading.value = true;
        const response = await list(parameters);

        if (val) {
          const needle = val.toLowerCase();
          tags.value = response.data.data.filter(
            (tag) => tag.title.toLowerCase().indexOf(needle) > -1
          );
        } else {
          tags.value = response.data.data;
        }

        loading.value = false;
      });
    };

    const onInput = (values: TagCodec[]) => {
      context.emit("update:modelValue", values);
    };

    return function render() {
      return h(QSelect, {
        modelValue: props.modelValue,
        multiple: true,
        options: tags.value,
        dense: true,
        optionLabel: "title",
        optionValue: "id",
        outlined: true,
        useInput: true,
        useChips: true,
        placeholder: "Start typing to search",
        onFilter: onFilterTest,
        "onUpdate:modelValue": onInput,
        loading: loading.value,
      });
    };
  },
});

What I have tried

I have tried to use the several props that is available for the component but nothing seemed to work.

My understanding is that whenever we want to create an AJAX request using QSelect we should use the onFilter event emitted by QSelect and handle the case from there.

Questions

  • Is this the way to create a Quasar AJAX Autocomplete? (I have tried to search online but all the answers are in Quasar's forums that are currently returning BAD GATEWAY)
  • What am I doing wrong that it is not displaying the dropdown as soon as I click on the QSelect?

Solution

  • It seems updateFn may not allow being async. Shift the async action a level up to solve the issue.

    const onFilterTest = async (val, update /* abort */) => {
      const parameters = val === '' ? {} : { title: val };
      loading.value = true;
    
      const response = await list(parameters);
      let list = response.data.data;
      if (val) {
        const needle = val.toLowerCase();
        list = response.data.data.filter((x) => x.title.toLowerCase()
          .includes(needle));
      }
      update(() => {
        tags.value = list;
        loading.value = false;
      });
    };
    

    I tested it by the following code and mocked values.

    // import type { PropType } from 'vue';
    import { defineComponent, h, ref } from 'vue';
    // import type { TagCodec } from "@/services/api/resources/tags/codec";
    // import { list } from "@/services/api/resources/tags/actions";
    import { QSelect } from 'quasar';
    
    export const TagAutoComplete = defineComponent({
      name: 'TagAutoComplete',
      props: {
        modelValue: { type: [] },
      },
      emits: ['update:modelValue'],
    
      setup(props, context) {
        const loading = ref(false);
    
        const tags = ref([]);
    
        const onFilterTest = async (val, update /* abort */) => {
          // const parameters = val === '' ? {} : { title: val };
          loading.value = true;
          const response = await new Promise((resolve) => {
            setTimeout(() => {
              resolve({
                data: {
                  data: [
                    {
                      id: 1,
                      title: 'Vue',
                    },
                    {
                      id: 2,
                      title: 'Vuex',
                    },
                    {
                      id: 3,
                      title: 'Nuxt',
                    },
                    {
                      id: 4,
                      title: 'SSR',
                    },
                  ],
                },
              });
            }, 3000);
          });
    
          let list = response.data.data;
          if (val) {
            const needle = val.toLowerCase();
            list = response.data.data.filter((x) => x.title.toLowerCase()
              .includes(needle));
          }
          update(() => {
            tags.value = list;
            loading.value = false;
          });
        };
    
        const onInput = (values) => {
          context.emit('update:modelValue', values);
        };
    
        return function render() {
          return h(QSelect, {
            modelValue: props.modelValue,
            multiple: true,
            options: tags.value,
            dense: true,
            optionLabel: 'title',
            optionValue: 'id',
            outlined: true,
            useInput: true,
            useChips: true,
            placeholder: 'Start typing to search',
            onFilter: onFilterTest,
            'onUpdate:modelValue': onInput,
            loading: loading.value,
          });
        };
      },
    });