Passing a single property to a composable in Vue3
Asked Answered
D

1

2

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>
Demimonde answered 27/5, 2022 at 18:54 Comment(3)
It seems you're not passing the src props correctly to the component which is why it's returning undefinedEurhythmics
@Eurhythmics The issue then is that it is not reactive. When the src updates, the composable does not.Demimonde
See answer here -> https://mcmap.net/q/649186/-use-props-in-composables-vue3Hogwash
P
8

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:

  1. 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.

  2. 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 */) ✅
    
  3. 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

Pyrrhonism answered 28/5, 2022 at 5:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.