Skip to main content

Vue.js

ElectricSQL integrates with Vue via a dependency injection and the Reactivity API.

The dependency injection provides your Electric Client to your components. The reactivity API is used to bind live queries to your components.

Dependency Injection

makeElectricDependencyInjector

In Vue.js, dependency injection provides a way to pass data through the component tree without having to pass props down manually at every level. ElectricSQL provides a makeElectricDependencyInjector function that constructs a provideElectric provider method and an injectElectric injector method.

import { makeElectricDependencyInjector } from 'electric-sql/vuejs'
import { Electric } from './generated/client'

const {
provideElectric,
injectElectric
} = makeElectricDependencyInjector<Electric>()

You typically call this once per app as part of your instantiation code. You then use the provide and inject methods in tandem to pass down and access the client in your components.

info

We provide this dynamic API rather than static provideElectric and injectElectric imports in order to preserve the type information about your database structure. As you can see from the example above, the context is constructed using the Electric type argument, which is a generated type containing all of the information about your database structure. This then allows you to write type safe data access code.

provideElectric and injectElectric

provideElectric is a provider method that injects the Electric Client instance to the rest of the app under an Electric-specific symbol key, so it will never clash with other dependency injections. You can call it within the context of a provider-like component, e.g.:

// ElectricProvider.vue
<script lang="ts" setup>
import { onMounted, shallowRef } from 'vue'
import { ElectricDatabase, electrify } from 'electric-sql/wa-sqlite'
import { insecureAuthToken } from 'electric-sql/auth'
import { provideElectric } from './electric'
import { Electric, schema } from './generated/client'

// use shallow reference for the client as deep reactivity is not
// necessary and likely to cause issues
const electric = shallowRef<Electric>()

onMounted(async () => {
const conn = await ElectricDatabase.init('electric.db')
const client = await electrify(conn, schema)
await client.connect(insecureAuthToken({ sub: 'dummy' }))

// update the reference with client instance
electric.value = client
})

// provide the client to the rest of the app
provideElectric(electric)
</script>

<template>
<div v-if="electric">
<slot />
</div>
</template>

With a provideElectric call in a parent component in place, you can then access the electric client instance using the injectElectric method, e.g.:

<script lang="ts" setup>
import { ref } from 'vue'
import { injectElectric } from './electric'

const { db } = injectElectric()!
const value = ref()

const generate = async () => {
const { newValue } = await db.rawQuery({
sql: 'select random() as newValue'
})
value.value = newValue
}
</script>

<template>
<div>
<p>{{ value }}</p>
<a @click="generate"> Generate ↺ </a>
</div>
</template>

Reactive API

useLiveQuery

useLiveQuery sets up a live query (aka a dynamic or reactive query). This takes query function returned by one of the db.live* methods and keeps the results in sync whenever the relevant data changes.

<script lang="ts" setup>
import { computed } from 'vue'
import { useLiveQuery } from 'electric-sql/vuejs'
import { injectElectric } from './electric'

const { db } = useElectric()!

// Use the query builder API.
const { results } = useLiveQuery(db.items.liveMany())

// Use the raw SQL API.
const { results: countResults } = useLiveQuery(
db.liveRawQuery({
sql: 'select count(*) from items'
})
)

const items: Item[] = computed(() => results ?? [])

const count: number = computed(
() => countResults.value !== undefined
? countResults.value[0].count
: items.value.length
)
</script>

<template>
<div>
<p>
{{ count }}
{{ count === 1 ? 'item' : 'items' }}
</p>
<ul>
{{items.map((item, index) => (
<li key={ index }>
Item: { item.value }
</li>
))}}
</ul>
</div>
</template>

The full return value of the live query method is:

const { results, error, updatedAt } = useLiveQuery(runQuery)

Where all destructured values are read-only reactive ref objects which can be used with all of Vue reactivity APIs such as computed and watchEffect.

Running the query successfully will assign a new array of rows to the results and the error will be undefined. Or if the query errors, the error will be assigned to the error and results will be undefined. The updatedAt ref object is a Date instance set when the return value last changed. Which is either when the query is first run or whenever it's re-run following a data change event.

See the implementation in frameworks/vuejs/reactive/useLiveQuery.ts for more details.

Query dependencies

The live query is always re-run when any of the data in any of the tables it depends on changes. When using the static form of useLiveQuery that takes a live query as an argument, the results will not be reactive with respect to the query parameters.

To make the results reactive with respect to the query parameters, you can use the dynamic form of useLiveQuery that takes a function that returns a live query, or a reference to a live query. Under the hood, the live query will be recomputed when any of the query parameters change by observing the resulting query string. This means that the query function will be re-run when any of the parameters change, e.g.:

const status = ref(true)

const { results } = useLiveQuery(
() => db.projects.liveMany({
where: { status: status }
})
)

// `results` will be recomputed on data changes
// and anytime the `status` flag changes

// ...

With this API, any change to the query dependencies will cause it to recompute. You can exert more control over this recomputation by passing an explicit list of Watch Sources as a second argument to useLiveQuery, such that the query is recomputed when any of the provided watch sources changes:

const status = ref(true)
const filter = ref('@example.com')

const { results } = useLiveQuery(
() => db.projects.liveMany({
where: {
status: status,
email: { endsWith: filter }
}
}),
[ status ]
)

// `results` will be recomputed on data changes
// and anytime the `status` flag changes, but not
// when the `filter` changes

// ...