Trying to understand QDialog and $refs



  • I’ve run into some weird behavior with QDialog and with $refs. I don’t know if they’re the same problem or two separate issues. Here it goes.

    I have a component called UserRegistration, that is essentially a dialog that collects the user data for registration. So it looks something like:

    <template>
      <q-dialog v-model="isOpen" persistent>
        <q-card>
          <q-bar dark class="bg-primary text-white">
            <div>User registration</div>
            <q-btn dense flat rounded icon="close" @click="handleCancel">
              <q-tooltip>Close</q-tooltip>
            </q-btn>
          </q-bar>
          <q-card-section>
            <record-input-form ref="inputForm"
              :currentRecord="this.record"
              :schema="this.schema"
              :defaultRecord="this.defaultRecord" />
          </q-card-section>
          <q-card-actions align="right">
            <q-btn flat color="primary" label="OK" @click="handleOK" />
            <q-btn flat color="secondary" label="Cancel" @click="handleCancel" />
          </q-card-actions>
        </q-card>
      </q-dialog>
    </template>
    

    The RecordInputForm is a list of QField, one for each field in this.record as defined by this.schema.

    The first time I ran the code I encountered problem #1: this.$refs was an empty object. I used console.log to look for its value in created, mounted and in handleOK. In all three cases it was empty, no fields whatsoever. This does not make sense, since I have a ref="inputForm" set on the <record-input-form> component.

    I set a ref on the QDialog and then I started seeing the value of $refs as:

    {
      dialog: ....,    // QDialog component
      inputForm: undefined
    }
    

    Again this happened on created, mounted and in handleOK. I figured maybe in created and mounted the content of the dialog is not actually created, but in handleOK it should be, since it is displayed. So I added

    console.log(this.$refs.inputForm);
    

    to the handleOK. And that returned a Vue component that was the RecordInputForm. At the same time though, $refs showed an inputForm field with an undefined value.

    So I am looking for some kind of explanation of what happens here. When are components that are the contents of a QDialog created? And more importantly, when would a component that has a ref attribute show in $refs?

    Problem #2 showed when I started looking at the Vue dev tools in Chrome. While the QDialog is listed in the component tree, it shows no children, even when the dialog is displayed. Not only that, but using the finder to locate for example the OK button, locates nothing. It’s as if the QDialog is empty, no children whatsoever. Which I know it is not true, since I wrote the code, and I can see the content on the page.

    I will mention problem #3, although I think it deserves a separate discussion. Debugging does not work. VS Code refuses to set breakpoints, and that’s the end of that. debugger statements are not reliable. Sometimes they work, other times they don’t. And sometimes it stopped in code from one or the other package that I use in the project, without me setting any kind of breakpoint there. Really weird behavior. Quasar 0.x did not have this problem. Something got broken in 1.0 and it needs a fix. Badly. The Quasar docs have a section about debugging in VS Code, but it is empty.



  • @tdumitr if you are accessing your ref in js part of your sfc, since its referencing a component that will be rendered at later time, just wrap your $refs call inside a $nextTick(()=>{ this.$refs.yourRef.someMethod()}). https://vuejsdevelopers.com/2019/01/22/vue-what-is-next-tick/



  • Hah, I forgot to mention in my original post that I tried that too. It does not work. The reason is probably what the Vue dev tools in Chrome reveal.

    The content of the QDialog does not show up in the Vue dev tools even when the dialog is open. It should have a QCard child with two QCardSection and one QCardActions under it and so on. Please see the attached screen shot.

    Screen shot here

    Your answer seems to suggest that QDialog does not render at the same time as the component containing it. That makes sense, there’s no reason to take the time to render it until the QDialog is displayed. But that does not explain why in handleOK (which is called when the OK button in the QDialog is called) $refs.inputForm is undefined. At this time the QDialog is obviously fully rendered.

    Here is a console trace where I load the page, open the registration dialog and then press the OK button in the dialog.

    in mounted
    this.$nextTick(() => console.log(this.$refs));
    {}
        __proto__: Object
    
    in handleOK
    this.$refs
    {inputForm: VueComponent}
      inputForm: undefined
      __proto__: Object
    this.$refs.inputForm
    VueComponent {_uid: 53, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: VueComponent, …}
    this.$children
    [VueComponent]
      0: VueComponent {_uid: 39, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: VueComponent, …}
      length: 1
      __proto__: Array(0)
    this.$children[0].$children
    []
    

    So in mounted, this.$refs is empty with or without using $nextTick.

    In handleOK, this.$refs shows a field inputForm that is undefined, and that is contradicted by the line immediately below that shoes this.$refs.inputForm as being a VueComponent. Equally interesting is this.$children that shows a single child: the QDialog and this.$children[0].$children that is an empty array. In other words, even with the dialog open, the QDialog has no children, consistent with what the Vue dev tools shows. And yet the page clearly displays content in the QDialog.

    To properly display the content of the dialog, I need to interrogate the API that backs the app for the default record and for the record schema and pass it on to the inputForm component which uses that to build its content. With Quasar 0.X, I was doing this in created and everything was OK, because $refs was already populated. With Quasar 1.0 I clearly cannot do that. But I am unclear when can I pass on the data to the inputForm. Do I do this after the QDialog is displayed? Will $refs have the inputForm field populated at that point?



  • Weird, i did encounter something like this before v1 but it was for panels, can try to wrap your dialog inside keep-alive. Can maybe provide a minimal pen of your setup so we can look it up.
    edit. nvm, that didn’t work.

    what you could do is pass your schema as a prop to your Qdialog, then down to your child components that will use it. like so https://0ybb3.sse.codesandbox.io/dialog-sample, source https://codesandbox.io/s/0ybb3. (Vuex will work too)

    you are correct with your observations, i think this could be an issue about portal-vue which QDialog component is using (iirc), child components are getting recreated*. https://github.com/LinusBorg/portal-vue/issues/118



  • I ran into a similar issue (not using refs though) … because I expected to see my components on my QDiloag in Chrome Dev Tools and couldn’t understand why they did not show.

    By using console.log in mounted of the QDialog, I determined that the components on the QDialog are recreated each time the QDialog is opened … I am opening, closing, and re-opening the QDialog several times hence how I first noticed this.

    I ended up making use of Vuex because of this.



  • Yes, I was able to observe the same. The behavior is different from the old QModal, where everything was rendered when the page was loaded, with the dialog kept hidden, but very much in the DOM. There’s an opportunity for improving the docs (they are really great as they are, but there’s always room for improvement) to explain exactly what gets created when, when can one use refs for fields inside the dialog, etc.

    I moved the code for processing the schema and populating the dialog on this.$nextTick right after the dialog is open. Everything works well now.

    Thank you @metalsadman and @digiproduct for your help. Really appreciate it.


Log in to reply