DarkLite1
DarkLite1

Reputation: 14705

Vue composable scoping issue

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?

Upvotes: 0

Views: 1685

Answers (1)

DarkLite1
DarkLite1

Reputation: 14705

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 }
  },
})

Upvotes: 2

Related Questions