JSFiddle - React, Tailwind, and code Playground

by skirtle

HTML

<script src="https://unpkg.com/[email protected]/dist/vue.global.js"></script>
<div id="app">
  <my-child :key="key"></my-child>
  <button @click="key++">
    Click to break the size updates
  </button>
</div>

JavaScript

// This example illustrates how a computed ref is 'destroyed'.
// In this example the ref is created while the `setup` function
// of the child component is running. Because of that it is tied
// to that component and is destroyed when that component is
// destroyed. However, in this case that is undesirable because the
// computed ref is not specifically tied to that component, it just
// happens to be lazily created at that point. There are simple
// ways to work around this but it isn't immediately obvious why it
// happens unless you know about the destruction magic.


let width = null
let height = null
let size = null

// A composable for tracking the size of the browser viewport.
// As there can only be one viewport it tries to reuse as much
// as possible between the components that use it.
const useViewportSize = () => {
  // Lazily create everything the first time we're used
  if (!width) {
    width = Vue.ref(null)
    height = Vue.ref(null)
    
    // This is the problem. The lazy creation of this
    // computed ref occurs while setup is running
    size = Vue.computed(() => `${width.value} x ${height.value}`)
    
    const onResize = () => {
      width.value = window.innerWidth
      height.value = window.innerHeight
    }
    
    // Trigger immediately
    onResize()
    
    window.addEventListener('resize', onResize)
  }
  
  return {
    width, height, size
  }
}

const MyChild = {
  template: `<div>{{ size }}</div>`,
  
  setup () {
    const { size } = useViewportSize()
  
    return {
      size
    }
  }
}

Vue.createApp({
  components: { MyChild },
  
  data () {
    // The key is used to force the component to be destroyed
    return { key: 1 }
  }
}).mount('#app')