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
Related
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 };
}
})
I'm trying to develop microfrontend with Vuejs and Module Federation. So, i have method loadModule, that returns me a an object(maybe component), but what i should do with it i don't know. I tried to import this component like defineAsyncComponent but it's useless.
And i'm tried use the render function in this object and paste this it to markup.
The screenshot shows what the object I get, but I don’t know what to do next with it and how to register this like component.
I'm stumped, any advice would be welcome
<script setup>
import { ref, onMounted, defineAsyncComponent, h } from 'vue'
import TestComp from './TestComp.vue'
let Header
const modules = [
{
protocol: 'http',
host: 'localhost',
port: 8080,
moduleName: 'Company',
fileName: 'remoteEntry.js',
componentNames: ['Header'],
},
]
onMounted(() => {
modules.forEach((uiApplication) => {
const remoteURL = `${uiApplication.protocol}://${uiApplication.host}:${uiApplication.port}/${uiApplication.fileName}`
const { componentNames } = uiApplication
const { moduleName } = uiApplication
const element = document.createElement('script')
element.type = 'text/javascript'
element.async = true
element.src = remoteURL
element.onload = () => {
componentNames?.forEach((componentName) => {
const component = loadComponent(moduleName, `./${componentName}`)
component().then((res) => {
Header = res
console.log(res)
// what should i do next?
// This is not working
// Header = defineAsyncComponent(() => import('Company/Header'))
})
})
}
document.head.appendChild(element)
})
})[enter image description here][1]
function loadComponent(scope, module) {
return async () => {
// Initializes the shared scope. Fills it with known provided modules from this build and all remotes
await __webpack_init_sharing__('default')
const container = window[scope] // or get the container somewhere else
// Initialize the container, it may provide shared modules
await container.init(__webpack_share_scopes__.default)
const factory = await window[scope].get(module)
const Module = factory()
return Module
}
}
const remoteImport = async (location, name, options) => {
const module = await importResource(location, options)
if (name) {
let m = module[name]
if (!m) {
throw new Error(
`No component named ${name} founded in component ${location}`
)
}
return module[name]
} else {
return module
}
}
</script>
<template>
<div>
<Header ref="Header" title="Test shop" />
<h2>Our Shop Page</h2>
</div>
</template>
i create a composable function to get current coordinates from geolocation and returned correctly, but when i acces in my script setup always returned 0
here is my composable useGeolocation.js
import { onUnmounted, onMounted, ref } from 'vue'
export function useGeolocation(){
const kordinat = ref({ latitude: 0, longitude: 0 })
const isSupported = 'navigator' in window && 'geolocation' in navigator
let watcher = null
onMounted(() => {
if(isSupported)
watcher = navigator.geolocation.watchPosition(
position => (kordinat.value = position.coords)
)
})
onUnmounted(()=> {
if (watcher) navigator.geolocation.clearWatch(watcher)
})
return { kordinat, isSupported }
}
and how I use in my component
<script setup>
import { useGeolocation } from '#/composables/useGeoLocation'
const {kordinat} = useGeolocation()
const posisiUser = computed(()=>({
lat : kordinat.value.latitude,
lng : kordinat.value.longitude
}))
//...
</script>
in template i can display coordinates correctly with <h2>Coords : {{posisiUser.lat}}, {{posisiUser.lng}}</h2>
Coords : -6.22592, 106.8302336
but when i log console.log({p:posisiUser.value}) it returns '{"p":{"lat":0,"lng":0}}'
I need to be able access the correct coordinates for further usage. What is wrong?
here is sandbox link
You're logging the value of the computed prop immediately, which is not yet updated:
<script setup>
import { computed, ref, onMounted } from "vue";
import { useGeolocation } from "./composables/useGeoLocation";
const { kordinat } = useGeolocation();
const posisiUser = computed(() => ({
lat: kordinat.value.latitude,
lng: kordinat.value.longitude,
}));
console.log({ p: posisiUser.value }); // ❌ posisiUser not yet updated in useGeolocation()
</script>
If you want to log every change to the value, you can use a watchEffect():
watchEffect(() => {
console.log({ p: posisiUser.value });
})
To watch it only once, you can do:
const unwatch = watchEffect(() => {
unwatch();
console.log({ p: posisiUser.value });
})
demo
I am upgrading an app from vue 2 to vue 3 and I am having some issues with composables. I'd like to use props in the composable but it doesn't seem to be working. The code sample is pulled from a working component and works fine when I leave it in the component.
I assume defineProps isn't supported by composables, but then I am unclear how to handle it. When I pass the src in the parameters it loses its reactivity.
// loadImage.js
import { defineProps, onMounted, ref, watch } from 'vue'
// by convention, composable function names start with "use"
export function useLoadImage() {
let loadingImage = ref(true)
let showImage = ref(false)
const props = defineProps({
src: String,
})
const delayShowImage = () => {
setTimeout(() => {
showImage.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 (props.src) {
loadImage(props.src)
}
})
watch(
() => props.src,
(val) => {
if (val) {
loadingImage.value = true
loadImage(val)
}
},
)
// expose managed state as return value
return { loadingImage, showImage }
}
Edit
This method worked for me, but the two methods mentioned in the comments below did not.
I have a new question here.
// loadImage.js
import { onMounted, ref, watch } from 'vue'
// by convention, composable function names start with "use"
export function useLoadImage(props) {
let loadingImage = ref(true)
let showImage = ref(false)
const delayShowImage = () => {
setTimeout(() => {
showImage.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 (props.src) {
loadImage(props.src)
}
})
watch(
() => props.src,
(val) => {
if (val) {
loadingImage.value = true
loadImage(val)
}
},
)
// expose managed state as return value
return { loadingImage, showImage }
}
<script setup>
import { defineProps, toRef } from 'vue'
import { useLoadImage } from '../../composables/loadImage'
const props = defineProps({
src: String
})
const { loading, show } = useLoadImage(props)
</script>
According to official docs :
defineProps and defineEmits are compiler macros only usable inside <script setup>
You should pass the props as parameter without destructing them to not loose the reactivity :
export function useLoadImage(props) {
....
}
you can use useRef to pass specific props without losing reactivity
const imgRef = toRef(props, "img");
const { loding, show } = useLoadImage(imgRef);
I am building a project with Nuxt and I need to know the size of the wrapper to adjust the grid setting
(I want a single line, I could still do this in pure CSS probably by hiding the items)
It's my first time using composition API & script setup
<script setup>
const props = defineProps({
name: String
})
const width = ref(0)
const wrapper = ref(null)
const maxColumns = computed(() => {
if (width.value < 800) return 3
if (width.value < 1000) return 4
return 5
})
onMounted(() => {
width.value = wrapper.value.clientWidth
window.onresize = () => {
width.value = wrapper.value.clientWidth
console.log(width.value);
};
})
</script>
<template>
<div class="category-preview" ref="wrapper">
...
</div>
</template>
The console log is working properly, resizing the window and refreshing the page will return 3, 4 or 5 depending on the size, but resizing won't trigger the computed value to change
What am I missing ?
In my test enviroment I had to rename your ref 'width' into something else. After that it did worked for me with a different approach using an event listener for resize events.
You can do something like this:
<script setup>
import { ref, onMounted, onUnmounted, computed } from 'vue'
const wrapperWidth = ref(0)
const wrapper = ref(null)
// init component
onMounted(() => {
getDimensions()
window.addEventListener('resize', debounce(() => getDimensions(), 250))
})
// remove event listener after destroying the component
onUnmounted(() => {
window.removeEventListener('resize', debounce)
})
// your computed property
const maxColumns = computed(() => {
if (wrapperWidth.value < 800) {
return 3
} else if (wrapperWidth.value < 1000) {
return 4
} else {
return 5
}
})
// get template ref dimensions
function getDimensions () {
const { width } = wrapper.value.getBoundingClientRect()
wrapperWidth.value = width
}
// wait to call getDimensions()
// it's just a function I have found on the web...
// there is no need to call getDimensions() after every pixel have changed
const debounce = (func, wait) => {
let timeout
return function executedFunction (...args) {
const later = () => {
timeout = null
func(...args)
}
clearTimeout(timeout)
timeout = setTimeout(later, wait)
}
}
</script>
<template>
<div ref="wrapper">
{{ maxColumns }} // will change after resize events
</div>
</template>