Frameworks

ElectricSQL is designed to integrate with existing reactive component and frameworks/libraries and ORMs. It also provides a notifier API for generic integrations.

React

ElectricSQL integrates with React via a context provider and hooks. You can see this in action by looking at the source code of the React Native example app.

ElectricProvider context provider

In React, context provides a way to pass data through the component tree without having to pass props down manually at every level. A Context.Provider is a wrapper component that’s used to pass context down to its descendents in the component hierarchy.

ElectricSQL provides an ElectricProvider that accepts an electrified database driver instance as it’s value. Use it as one of the root components that wrap your app. For example, the code below illustrates an ExampleApp that opens and electrifies a database client and then provides it as the context value to the wrapped child components.

import React, { useEffect, useState } from 'react'
import SQLite from 'react-native-sqlite-storage'

import { electrify } from 'electric-sql/react-native'
import { ElectricProvider, useElectric, useElectricQuery } from 'electric-sql/react'

import { data } from '../migrations'

const config = {
  app: '<YOUR APP SLUG>',
  migrations: data.migrations
}

export const ExampleApp = () => {
  const [ db, setDb ] = useState()

  useEffect(() => {
    const promisesEnabled = true
    SQLite.enablePromise(promisesEnabled)

    SQLite.openDatabase('example.db')
      .then((original) => electrify(original, promisesEnabled, config))
      .then((db) => setDb(db))
  }, [])

  if (db === undefined) {
    return null
  }

  return (
    <ElectricProvider db={db}>
      {/* child components go here */}
    </ElectricProvider>
  )
}

With an ElectricProvider in place, you can then interact with the db via hooks.

useElectric hook

The useElectric hook simply returns the db value, which you can then use as you wish. For example below we insert a row with a random value into the database every time a user presses a button.

import React from 'react'
import { Pressable, Text, View } from 'react-native'

import { useElectric } from 'electric-sql/react'

const ExampleComponent = () => {
  const db = useElectric()

  const insertItem = () => {
    const randomValue = Math.random().toString(16).substr(2)

    db.transaction((tx) => {
      tx.executeSql('INSERT INTO items VALUES(?)', [randomValue])
    })
  }

  return (
    <View>
      <Pressable onPress={addItem}>
        <Text>Insert</Text>
      </Pressable>
    </View>
  )
}

useElectricQuery hook

useElectricQuery sets up a live query (aka a dynamic or reactive query). This takes an sql statement, optional bind parameters and keeps the results in sync whenever data changes.

import React from 'react'
import { Text, View } from 'react-native'

import { useElectricQuery } from 'electric-sql/react'

const ExampleComponent = () => {
  const { results } = useElectricQuery('SELECT value FROM items', [])

  return (
    <View>
      {results.map((item, index) => (
        <Text key={ index } style={styles.item}>
          Item: { item.value }
        </Text>
      ))}
    </View>
  )
}

The full return value of the hook is:

const { results, error, updatedAt } = useElectricQuery(sql, bindParams?)

Running the query successfully will assign a new array of rows to the results and error will be undefined. Or if the query errors, the error will be assigned to the error variable and results will be undefined. The updatedAt variable 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/react/hooks.ts for more details.

Putting it together

See the React Native and React examples in the example applications repo.

Prisma

Prisma supports SQLite. However, it doesn’t yet support SQLite in the client. As such, support is limited to the Node driver.

Todo: publish Prisma example and document usage here.

TypeORM

Todo: publish TypeORM example and document usage here.

Generic

It’s possible to use the Notifier API to integrate ElectricSQL with any reactive component framework. The trick is to subscribe to data change events and re-query when query results may have been affected.

Because you’re querying a local database, it’s usually very fast to perform a query. Typically it’s faster to just ask the database than to maintain a caching layer. For example, the implementation in the useElectricQuery hook simply monitors changes by tablename.

const handleChange = (notification: ChangeNotification): void => {
  // Reduces the `ChangeNotification` to an array of namespaced tablenames,
  // in a way that supports both the main namespace for the primary database
  // and aliases for any attached databases.
  const changedTablenames = notifier.alias(notification)

  if (hasIntersection(tablenames, changedTablenames)) {
    bustCache()
  }
}

const key = notifier.subscribeToDataChanges(handleChange)

Next steps