3

I stumbled into a totally unexpected problem while refactoring my code to composition API: there doesn't seem to be any (documented) way of accessing current instance from the lifecycle hooks.

sample code:

import { defineComponent, onMounted } from 'vue';

export default defineComponent({
  setup() {
    onMounted(() => { 
      console.log(this);  // <-- will be undefined
    });
  },
  mounted() {
    console.log(this);  // <-- will be the component
  },
}

I've spent hours trying to find a solution to this and ultimately just used the old options API to get what I want. None of examples, tutorials or documentation - that I read - use this in the hooks.

But I find it unbelievable that only undocumented getCurrentInstance would be the way to get the current instance from the hook.

So, which doc did I miss?

2
  • 1
    Option API holds every component in an object. Composition API compose components made from setup function. If you don't use onMounted() web hook in setup function, the component won't have a onMounted() lifecycle hook. Some life cycle hooks are gone because setup() is a life cycle hook itself, so you won't have beforeCreate() and created() hooks. Commented Jan 12, 2023 at 17:43
  • The reason you can't access this in the setup function is that you don't need it anymore Commented Jan 13, 2023 at 1:57

2 Answers 2

1

UPDATE 2.

You can mix Composition API and Options API to access this, if you need it.

Using this in Options API you can access all objects defined inside setup like variables, methods, computed, refs and etc.

But you cannot access anything defined using Options API inside setup.

The data provided over Options API is mixed with the data from setup. The objects from setup overwrite the definitions provided over Options API.

See how Vue mixes the data properties in the following example.

const { createApp, ref } = Vue;

const MyComponent = {
  setup() {
    // there is no way to access the `optionsVar` inside the `setup`
    const setupVar = ref('setupVar content')
    const collisionVar = ref('content from setup')
    const collisionObj = ref({ setup: true })
    return { setupVar, collisionVar, collisionObj }
  },
  data() {
     return {
        optionsVar: 'optionsVar content',
        collisionVar: 'content from data()',
        collisionObj: { options: true } 
     }
  },
  methods: {
    log() {
       console.log(`setupVar: ${this.setupVar}`)
       console.log(`collisionVar : ${this.collisionVar}`)
       console.log(`collisionObj: ${JSON.stringify(this.collisionObj)}`)
    }
  },
 template: `<button type="button" @click="log()">log()</button>`  
}

 const App = {
  components: {
    MyComponent
  }
}

const app = createApp(App)
app.mount('#app')
<div id="app">        
   <my-component />
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>

UPDATE 1.

Here is the same example with a component

const { createApp, ref, onMounted } = Vue;

const MyComponent = {
  setup() {
    const id = ref(Math.round(Math.random() * 100000));        
    const count = ref(0);        
    const plus = () => { count.value++; }        
    const minus = function() { count.value--; }        
    
    onMounted(() => { 
      count.value = Math.round(Math.random() * 10)
    });    
    
    return {id, count, plus, minus }
  },
  template: `id: {{id}} &nbsp; <button type="button" @click="minus()">-1</button>
    &nbsp;{{count}}&nbsp;
    <button type="button" @click="plus()">+1</button><hr/>`   
}

const App = {
  components: {
    MyComponent
  }
}

const app = createApp(App)
app.mount('#app')
<div id="app">        
   <my-component v-for="i in 5" />
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>

What for do you need this in the component?

If you create your component with Composition API, then you can access all the properties directly, without using this.

Here is a very basic example:

const { createApp, ref, onMounted } = Vue;

const App = {
  setup() {    

    const count = ref(0);        
    const up = () => { count.value++; }        
    const down = function() { count.value--; }    
    
    onMounted(() => { 
      count.value = 10
    });    
    
    return {count, up, down }
  }
}

const app = createApp(App)
app.mount('#app')
<div id="app">        
    <button type="button" @click="down()">-1</button>
    {{count}}
    <button type="button" @click="up()">+1</button>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>

Sign up to request clarification or add additional context in comments.

5 Comments

I need this to be able to assign a unique ID to each instance and then check for that ID in render function. I believe your count variable provides a global variable that all instances share, not a per-instance variable.
Then simply move the count variable to the component.
I can see now that setup() is actually create from vue 2. variables used in setup are unique to instance already. Your answer was correct from the start and I have been reading the docs poorly...
Sometimes a developer might still want a reference to the component instance to either dump it to the console or place it into a global variable for inspection/debugging/troubleshooting. Is there really no way to do this?
You can achieve it mixing Composition API and Options API. Define a method using Options API where you still can access this
-1

In case you only want a clean composition API in your vue component, please follow my approach via testing.

The component:

<template>
  <div>Best component ever!!!</div>
</template>

<script setup>
const fetchAPIDefinitions = () => { 
   // some HTTP call
};

const methods= {
  handleActivated: ({ _fetchAPIDefinitions = fetchAPIDefinitions } = {}) => {
    searchValue.value = lastSearchedValue.value;
    _fetchAPIDefinitions();
  }
};

onActivated(() => {
  methods.handleActivated();
});

defineExpose({
  handleActivated: methods.handleActivated,
  methods
});
</script>


The test

it("should call handleActivated via onActivated", () => {
    const wrapper = setup();
    const handleActivatedSpy = jest.fn();
    wrapper.vm.handlers.handleActivated = handleActivatedSpy;

    const activatedHooks = wrapper.vm.$.a;
    if (activatedHooks && activatedHooks.length > 0) {
      activatedHooks.forEach((hook) => hook());
    }

    expect(handleActivatedSpy).toHaveBeenCalledTimes(1);
  });

enter image description here

Why this approach works?

  1. Closure captures handlers - the object reference, not the function

  2. methods.handleActivated is a property lookup - happens at runtime when the hook executes

  3. You can replace the property: wrapper.vm.methods.handleActivated = spy

  4. When the closure runs, it looks up handlers.handleActivated and finds your spy

The key insight:

  • Object reference (methods) is stable and captured

  • Object property (methods.handleActivated) can be replaced at runtime

  • JavaScript property lookup happens when executed, not when closure is created

This pattern leverages JavaScript's object property resolution to create an indirection layer that breaks the direct closure capture, making the function interceptable for testing.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.