I have a problem
I can access $ref in onmounted function but
note : getCurrentInstance imported
I am getting the following error in submit function
Uncaught TypeError: Cannot read properties of null (reading 'ctx')
const submit = () => {
getCurrentInstance().ctx.$refs.modalLoading.openModal()
}
onMounted(() => {
getCurrentInstance().ctx.$refs.modalLoading.closeModal()
})
As getCurrentInstance should normally not been used, you should follow the Template Refs guide.
<!—- inside the template —->
<my-modal ref="modalLoading" />
// your component
export default defineComponent({
setup() {
const modalLoading = ref(null);
const handleSubmit = () => {
If(!modalLoading.value) return;
modalLoading.value.openModal();
}
return { modalLoading };
}
})
Related
I am using a composable to load images in Vue3. I have been able to pass all the props successfully as one object, see this question, but I am unable to pass the one property I want to be reactive. I am fairly certain that the issue is that the property in undefined
// loadImage.js
import { onMounted, ref, watch } from 'vue'
// by convention, composable function names start with "use"
export function useLoadImage(src) {
let loading = ref(true)
let show = ref(false)
const delayShowImage = () => {
setTimeout(() => {
show.value = true
}, 100)
}
const loadImage = (src) => {
let img = new Image()
img.onload = (e) => {
loading.value = false
img.onload = undefined
img.src = undefined
img = undefined
delayShowImage()
}
img.src = src
}
onMounted(() => {
if (src) {
loadImage(src)
}
})
watch(
() => src,
(val) => {
if (val) {
loading.value = true
loadImage(val)
}
},
)
// expose managed state as return value
/**
* loading is the image is loading
* show is a delayed show for images that transition.
*/
return { loading, show }
}
The below method returns this in the console.log and does not error.
Proxy {src: undefined} undefined
<script setup>
import { defineProps, computed } from 'vue'
import { useLoadImage } from '../../composables/loadImage'
const props = defineProps({
src: String
})
console.log(props, props.src)
const srcRef = computed(() => props.src)
const { loading, show } = useLoadImage(srcRef)
</script>
The below method returns this in the console.log
Proxy {src: undefined} undefined
and gives the following error
TypeError: Cannot read properties of undefined (reading 'undefined')
<script setup>
import { defineProps, toRef } from 'vue'
import { useLoadImage } from '../../composables/loadImage'
const props = defineProps({
src: String
})
console.log(props, props.src)
const srcRef = toRef(props.src)
const { loading, show } = useLoadImage(srcRef)
</script>
As indicated in comments, it seems src is undefined in your component because you're probably not passing the prop correctly to the component.
Even if src were set with a string, there still would be a few other issues:
toRef's first argument should be a reactive object (i.e., props), and the second argument should be the name of a key (i.e., 'src'):
// MyComponent.vue
const srcRef = toRef(props.src) ❌
const srcRef = toRef(props, 'src') ✅
Note: It's also valid to use const srcRef = computed(() => props.src), as you were originally doing.
watch's first argument is a WatchSource. When WatchSource is a function dealing with a ref, it should return the ref's unwrapped value. Alternatively, the WatchSource can be the ref itself:
// loadImage.js
watch(() => srcRef, /* callback */) ❌
watch(() => srcRef.value, /* callback */) ✅
watch(srcRef, /* callback */) ✅
The composable receives the image source in a ref, and your onMounted() hook is passing that ref to loadImage(), which is actually expecting the string in the ref's unwrapped value:
// loadImage.js
onMounted(() => {
if (src) { ❌ /* src is a ref in this composable */
loadImage(src)
}
})
onMounted(() => {
if (src.value) { ✅
loadImage(src.value)
}
})
demo
I am using the structure below in my Vue.js web application. I am now trying to implement testing to it. But when trying to test the exampleOfFunction it says that this.exampleOfData2 is undefined.
<template>
*Some HTML*
</template>
<script>
*Some Imports*
export default {
data() {
return {
exampleOfData1: [],
exampleOfData2: 100
},
methods: {
exampleOfFunction:function(){
if(this.exampleOfData2 === 100)
{
return false;
}
return true;
},
created() {
},
mounted() {
}
}
</script>
In my testfile I then try to access the code above and I succeed with console.log(FileToTest.data()); I can see the values of data and I can access the function with FileToTest.methods.exampleOfFunction(); but when I call the function it says that this.exampleOfData2 is undefined.
It looks like you're using the component options definition instead of the component instance in your tests.
You should be creating a wrapper by mounting the component, and then you could access the component method via wrapper.vm:
import { shallowMount } from '#vue/test-utils'
import FileToTest from '#/components/FileToTest.vue'
describe('FileToTest', () => {
it('exampleOfFunction returns false by default', () => {
const wrapper = shallowMount(FileToTest)
expect(wrapper.vm.exampleOfFunction()).toBe(false)
})
it('exampleOfFunction returns true when data is not 100', () => {
const wrapper = shallowMount(FileToTest)
wrapper.setData({ exampleOfData2: 0 })
expect(wrapper.vm.exampleOfFunction()).toBe(true)
})
})
We're using Vue 2 with the Vue Composition API and we're trying to create a composable that will expose application preferences:
// useApplicationPreferences.ts
import { ref, watch } from '#vue/composition-api'
import { useSetDarkModeMutation, useViewerQuery } from 'src/graphql/generated/operations'
const darkMode = ref(false) // global scope
export const useApplicationPreferences = () => {
const { mutate: darkModeMutation } = useSetDarkModeMutation(() => ({
variables: {
darkMode: darkMode.value,
},
}))
watch(darkMode, async (newDarkMode) => {
console.log('darkMode: ', newDarkMode)
await darkModeMutation()
})
return { darkMode }
}
This code works fine but when the composable is used in two components that are rendered at the same time we can see that watch has been triggered twice. This is easily solved by moving the watch function to the global scope (outside the function).
However, the issue then is that we can't use the darkModeMutation. This graphql mutation can not be moved to the global scope outside of the function, if we do that the page doesn't even get rendered.
The goal is to have darkMode available in many places and when the value of the darkMode ref changes the mutation is only triggered once. How can this be achieved?
Solved the issue by creating a callable function that starts watch only when required (i.e. only once somewhere in the app).
// useApplicationPreferences.ts
import { ref, watch } from '#vue/composition-api'
import { useSetDarkModeMutation, useViewerQuery } from 'src/graphql/generated/operations'
const darkMode = ref(false) // global scope
export const useApplicationPreferences = () => {
const { mutate: darkModeMutation } = useSetDarkModeMutation(() => ({
variables: {
darkMode: darkMode.value,
},
}))
const startWatch = () => {
watch(darkMode, async (newDarkMode) => {
await darkModeMutation()
})
}
return { darkMode, startWatch }
}
Which the can be called once in MainLayout.vue:
// MainLayout.vue
import { defineComponent } from '#vue/composition-api'
import { useApplicationPreferences } from 'useApplicationPreferences'
export default defineComponent({
setup() {
const { startWatch } = useApplicationPreferences()
startWatch()
},
})
All other components can then simply consume (get/set) the darkMode ref as required while watch is only running once.
// Settings.vue
import { defineComponent } from '#vue/composition-api'
import { useApplicationPreferences } from 'useApplicationPreferences'
export default defineComponent({
setup() {
const { darkMode } = useApplicationPreferences()
return { darkMode }
},
})
I'm experiencing a bug in my nuxt application working with vuex. I'm trying to access a store getter using mapGetters helper but when I access to that property in beforeCreate() hook value is undefined.
store/user.js
import VuexPersistence from "vuex-persist";
export const plugins = [VuexPersistence];
export const state = () => ({
user: null,
});
export const getters = {
isLoggedIn(state) {
if (state && state.user) {
console.log("state.user", state.user);
}
return state.user !== null && state.user !== {};
},
};
mycomponent.vue
export default {
beforeCreate() {
const isLoggedIn = this.$store.getters["user/isLoggedIn"];
console.log("computed isLoggedIn", this.isLoggedIn);
console.log("isLoggedIn", isLoggedIn);
},
computed: {
...mapGetters(["user/isLoggedIn"]),
},
};
</script>
Here is the output result in browser console
The store is not available in the beforeCreate hook. You could move your code to the mounted() hook, but I would recommend placing it in a middleware for checking if the user is logged in.
middleware/auth-check.js
export default function ({ store }) {
const isLoggedIn = store.getters["user/isLoggedIn"];
// do something...
}
Then add to your page:
export default {
...
middleware: 'auth-check'
...
}
i have one async action vuex, im using map getters and component created function to fetch and fill data, if im using this store data inline object in template view console show error undefined, if i try acces variable only without inline object im getting undefined error for inline object, i think this error about async function not blocking main process component fully loaded and after async function filled variable
actions, state
// state
export const state = {
app: null
}
// getters
export const getters = {
app: state => state.app,
}
// mutations
export const mutations = {
[types.FETCH_APP_SUCCESS] (state, { app }) {
state.app = app
},
[types.FETCH_APP_FAILURE] (state) {
state.app = null
},
[types.UPDATE_APP] (state, { app }) {
state.app = app
}
}
async fetchApp ({ commit }) {
try {
const { data } = await axios.get('/api/app/1')
commit(types.FETCH_APP_SUCCESS, { app: data })
} catch (e) {
commit(types.FETCH_APP_FAILURE)
}
}
component
<template>
<div>
{{app.name}}
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
middleware: 'auth',
created () {
// i try here async and await
this.$store.dispatch('app/fetchApp')
},
computed: mapGetters({
app: 'app/app'
}),
metaInfo () {
return { title: this.$t('home') }
}
}
</script>
state is filled
variable can see in html
but console this error
app/app is initially null, and your template does not have a null check on app.name, which results in the error you saw. You can either conditionally render app.name in the template:
<template>
<div>
<template v-if="app">
{{app.name}}
</template>
</div>
</template>
Or use the empty string as app/app's initial state instead of null in your store.