1
votes

I've been experimenting with the new composition-api in VueJS and am not sure how to solve a problem. I'm looking for some advice on how to properly implement a solution. This wasn't a problem when everything was vuex-based since you can dispatch an action to another module without a problem. However, I'm struggling to find a solution for the composition implementation.

Problem:

  1. Component calls a CompositionA's function.
  2. CompositionA triggers a login function.
  3. On CompositionA's login success/failure response I would like to call a CompositionB function. (CompositionB contains data and logic for showing a snackbar that's used across the site)

The problem is that it is necessary to inject the snackbar dependency in every component rather than have it be instantiated/mounted from CompositionA. Current solution is to this effect:

Component.vue:

// template calls login(credentials) method
import { useCompositionA } from '@/compositions/compositionA'
import { useCompositionB } from '@/compositions/compositionB'
export default {
  name: 'Component',
  setup(props, context) {
    const { login } = useCompositionA(props, context, useCompositionB(props, context))
    return {
      login
    }
  },
}

compositionA.js:

export const useAuth = (props, context, snack) => {
  const login = async (credentials) => {
    try {
      return await loginWithEmailPassword(credentials)
      snack.show({text: 'Welcome back!'})
    } catch (err) {
      snack.show({text: 'Failed to login'})
    }
  }
  return { login }
}


compositionB.js:

export const useSnack = (props, context) => {
  const snack = reactive({
    color: 'success',
    text: null,
    timeout: 6000,
    visible: true,
  })

  const snackRefs = toRefs(snack)

  const show = ({ text, timeout, color }) => {
    snackRefs.text.value = text
    snackRefs.timeout.value = timeout || 6000
    snackRefs.color.value = color || 'success'
    snackRefs.visible.value = true
  }
  return { 
    ...snackRefs,
    show
  }
}

Would be nice if something like below existed, but I'm finding that the properties aren't reactive in CompositionB if it's used from CompositionA (method gets called but snackbar doesn't show up). My understanding is that Vue isn't injecting CompositionB into the Component, so I'm just running another instance of CompositionB inside CompositionA. What am I doing something wrong? What's the proper solution here?

compositionA.js (not working):

import { useCompositionB } from '@/compositions/compositionB'
export const useAuth = (props, context) => {
  const login = async (credentials) => {
    const { show } = useCompositionB()
    try {
      return await loginWithEmailPassword(credentials)
      show({text: 'Welcome back!'})
    } catch (err) {
      show({text: 'Failed to login'})
    }
  }
  return { login }
}

Thanks in advance,

1
The idea of compositions is to abstract functionality common to multiple components (as opposed to using mixins, for example). A snackbar doesn't really seem like a good candidate for a this, as you're not going to want multiple components controlling the rendering of the snackbar. I would still be using Vuex to manage this state.Nilson Jacques

1 Answers

2
votes

As expected it was due to the Component referencing its own local copy of CompositionB*. Solution is actually to bring the state of your compositions into the global scope according to:

https://vueschool.io/articles/vuejs-tutorials/state-management-with-composition-api/

Something like this:

compositionB.js:

const snack = reactive({
  color: 'success',
  text: null,
  timeout: 6000,
  visible: true,
})
export const useSnack = (props, context) => {

  const snackRefs = toRefs(snack)

  const show = ({ text, timeout, color }) => {
    snackRefs.text.value = text
    snackRefs.timeout.value = timeout || 6000
    snackRefs.color.value = color || 'success'
    snackRefs.visible.value = true
  }
  return { 
    ...snackRefs,
    show
  }
}

Works like a charm.

Only caveat I found initially was a composition-api error:

Uncaught Error: [vue-composition-api] must call Vue.use(plugin) before using any function.

This was easily solved by mounting the composition-api first thing in main.js as per solution here:

Uncaught Error: [vue-composition-api] must call Vue.use(plugin) before using any function

I think this won't be a problem with vue3 comes out. Hope this helps someone.