Add VueJS Composition API support to Vuefire
See original GitHub issueI really liked the simplicity of using this.$bind
and this.$rtdbBind
this project provides. Recently, I started to use the Composition API for my projects and unfortunately, these functions do not make much sense since there is no single this.data
reactive to bind firebase references to anymore.
Are you planning to add some sort of Composition API support to Vuefire anytime soon?
In the meantime, I came up with a workaround using the functions provided by vuefire-core
libary and created some sort of Composition API wrapper for them. I hope it can be useful to some of you. I tried to find an existing solution, but what I found did not support both database types and made a distinction between binding documents and collections (Link).
use-firestore.js
import {bindCollection, bindDocument, walkSet,} from '@posva/vuefire-core'
const ops = {
set: (target, key, value) => walkSet(target, key, value),
add: (array, index, data) => array.splice(index, 0, data),
remove: (array, index) => array.splice(index, 1),
}
const binds = []
export function useFirestore() {
function bind(vm, key, ref, options) {
return new Promise((resolve, reject) => {
let unbind
if ('where' in ref) {
unbind = bindCollection(
{
vm,
key,
ops,
collection: ref,
resolve,
reject,
},
options
)
} else {
unbind = bindDocument(
{
vm,
key,
ops,
document: ref,
resolve,
reject,
},
options
)
}
binds.push({vm, key, ref, unbind})
})
}
function unbind(vm, key, reset) {
const i = binds.findIndex(u => u.vm == vm && u.key == key)
if (i > -1) {
binds[i](reset)
binds.splice(i, 1)
}
}
return {
bind,
unbind
}
}
use-rtdb.js
import {rtdbBindAsArray as bindAsArray, rtdbBindAsObject as bindAsObject, walkSet} from '@posva/vuefire-core'
const ops = {
set: (target, key, value) => walkSet(target, key, value),
add: (array, index, data) => array.splice(index, 0, data),
remove: (array, index) => array.splice(index, 1),
}
const binds = []
export function useRTDB() {
function bind(vm, key, source, options) {
return new Promise((resolve, reject) => {
let unbind
if (Array.isArray(vm[key])) {
unbind = bindAsArray(
{
vm,
key,
collection: source,
resolve,
reject,
ops,
},
options
)
} else {
unbind = bindAsObject(
{
vm,
key,
document: source,
resolve,
reject,
ops,
},
options
)
}
binds.push({vm, key, source, unbind})
})
}
function unbind(vm, key, reset) {
const i = binds.findIndex(u => u.vm == vm && u.key == key)
if (i > -1) {
binds[i](reset)
binds.splice(i, 1)
}
}
return {
bind,
unbind
}
}
use-meeting-store.js
import {createStore} from "pinia";
import {useFirebase} from "./use-firebase";
import {useRTDB} from "./use-rtdb";
import {useFirestore} from "./use-firestore";
export const useMeetingStore = createStore({
id: 'meeting',
state: () => ({
id: null,
meeting: null,
participants: []
}),
getters: {
availableSeats(state) {
if (!isNaN(state.meeting?.maxParticipants)) {
const audience = state.participants.filter(p => !p.host)
return state.meeting.maxParticipants - audience.length
}
}
},
actions: {
bind(id) {
this.state.id = id
return Promise.all([
this.bindMeeting(),
this.bindParticipants()
])
},
bindMeeting() {
const {bind} = useFirestore()
const {firebase} = useFirebase()
return bind(
this.state,
'meeting',
firebase
.firestore()
.collection('meetings')
.doc(this.state.id)
)
},
bindParticipants() {
const {bind} = useRTDB()
const {firebase} = useFirebase()
return bind(
this.state,
'participants',
firebase
.database()
.ref(`/participants/`)
.orderByChild('meetingID')
.equalTo(this.state.id)
)
},
createParticipant() {
return new Promise((resolve, reject) => {
const auth = useAuthStore()
const {firebase} = useFirebase()
const database = firebase.database()
const participantRef = database.ref(`/participants/${auth.state.uid}`)
database.ref('.info/connected').on('value', snapshot => {
if (snapshot.val() == false) {
return
}
participantRef
.onDisconnect()
.remove()
.then(() => participantRef.set({
dateCreated: firebase.database.ServerValue.TIMESTAMP,
meetingID: this.state.id
}))
.then(resolve)
.catch(reject)
})
})
}
}
})
Splashscreen.vue
<template>
<div>
<div class="middle">
<h1 class="ui header">
JOIN
</h1>
<div class="ui tiny blue sliding indeterminate progress">
<div class="bar"></div>
</div>
</div>
<div class="bottom">
<p>by</p>
<img src="assets/pitcher-logo-COLOR-TM-300.png" alt="Pitcher AG">
</div>
</div>
</template>
<script>
import {onMounted} from '@vue/composition-api'
import {useAuth} from "../use-auth";
import {useMeetingStore} from "../use-meeting-store";
export default {
name: 'Splashscreen',
props: {
authToken: {
type: String
},
meetingID: {
required: true,
type: String
}
},
setup(props, {root}) {
const {login} = useAuth()
const meeting = useMeetingStore()
onMounted(async () => {
await login(props.authToken)
await meeting.bind(props.meetingID)
if (meeting.availableSeats === 0) {
return root.$router.push({name: 'full'})
}
await meeting.createParticipant()
root.$router.push({name: 'room'})
})
}
}
</script>
<style lang="less" scoped>
.middle {
left: 50%;
position: fixed;
top: 50%;
transform: translate(-50%, -50%);
.ui.header {
font-size: 96px;
}
}
.bottom {
bottom: 35px;
left: 0;
position: fixed;
right: 0;
text-align: center;
img {
max-width: 150px;
}
p {
margin-bottom: .5em;
}
}
</style>
Issue Analytics
- State:
- Created 3 years ago
- Reactions:10
- Comments:5 (2 by maintainers)
Top GitHub Comments
I don’t have plans right now to add it but it would make sense for Vue 3. Good news is, as you found out, the core is ready for this change because it binds data to the property of an object. Which means, it could easily work by receiving a
Ref
and binding to itsvalue
property, or to aReactive
object and a provided key property to bind to that property. I think that the next version of vuefire should reunite vuexfire and vuefire together and expose lower level functions that are in coreHi @electric-skeptic,
the project has changed quite a bit since I last posted March 22. I can recommend having a look at the guten-abend project.
The functions I created for the RTDB:
useRef.js
useLiveRef.js
I prefer guten-abend’s syntax. You can use the documents directly in the component or wrap them in another function (i.e.
useMeeting
oruseParticipants
).I went back to just having a simple
firebase.js
initializing and exporting the components I need. I believe the use-functions are only necessary for view-states. But I’m still trying to figure out how to structure and divide things in Vue 3.source
Kind Regards Joshua