react-query-cache-persistent
TypeScript icon, indicating that this package has built-in type declarations

1.0.0 • Public • Published

react-query-cache-persistent

This persistor extends QueryCache to persist the cache in a simple way without hydration/dehydration phase.

The biggest advantage in terms of performance is, that the persistor stores the queries by each single query instead of storing the whole cache on each change.

This is huge if your cache is several megabytes in size.

⚠️ The only drawback is that this only works in a synchronous way. You cannot use this if your storage only provides asynchronous methods to get/set the cache. See How it works

Usage

See below for examples.

import { PersistentQueryCache } from 'react-query-cache-persistent'

// Your implementation. See below for examples.
const persistentQueryCache = new PersistentQueryCache({
  add: (query) => {},
  updated: (query) => {},
  removed: (query) => {},
})

const queryClient = new QueryClient({ queryCache: persistentQueryCache })

Examples

Web

Uses localStorage as storage.

localStorage

import { PersistentQueryCache } from 'react-query-cache-persistent'

const persistentQueryCache = new PersistentQueryCache({
  add: (query) => {
    const item = window.localStorage.getItem(`queryCache.${query.queryHash}`)

    if (item != null) {
      query.state = JSON.parse(item)
    }

    window.localStorage.setItem(`queryCache.${query.queryHash}`, JSON.stringify(query.state))
  },
  updated: (query) => {
    window.localStorage.setItem(`queryCache.${query.queryHash}`, JSON.stringify(query.state))
  },
  removed: (query) => {
    window.localStorage.removeItem(`queryCache.${query.queryHash}`)
  },
})

const queryClient = new QueryClient({ queryCache: persistentQueryCache })

React Native

Uses @op-engineering/op-sqlite as storage. But you can use any sqlite solution as long it supports synchronous get/set.

The example below writes each query as a table row into a sqlite database

queryCache.sqlite3

import {
  ANDROID_DATABASE_PATH,
  IOS_LIBRARY_PATH,
  PreparedStatementObj,
  open,
} from '@op-engineering/op-sqlite'
import { Query } from '@tanstack/react-query'
import { Platform } from 'react-native'

import { PersistentQueryCache } from 'react-query-cache-persistent'

const tableName = 'queryCache'

const connect = () => {
  return open({
    name: 'queryCache.sqlite3',
    location: Platform.OS === 'ios' ? IOS_LIBRARY_PATH : ANDROID_DATABASE_PATH,
  })
}

let db = connect()

db.execute(
  `CREATE TABLE IF NOT EXISTS ${tableName} (queryHash TEXT NOT NULL UNIQUE, queryState TEXT) STRICT;`
)

const selectStmt = db.prepareStatement(`SELECT queryState FROM ${tableName} WHERE queryHash = ?;`)
const insertStmt = db.prepareStatement(
  `INSERT INTO ${tableName} (queryHash, queryState) VALUES (?, ?) ON CONFLICT(queryHash) DO UPDATE SET queryState=excluded.queryState;`
)
const deleteStmt = db.prepareStatement(`DELETE FROM ${tableName} WHERE queryHash = ?;`)

/**
 * Executes the prepared statement with the given parameters
 *
 * Will retry once if it throws `[OP-SQLite] DB is not open`
 */
const runStmt = (stmt: PreparedStatementObj, params: string[]) => {
  try {
    stmt.bind(params)
    return stmt.execute()
  } catch (error: unknown) {
    if (`${error}`.includes('[OP-SQLite] DB is not open')) {
      // retry once (on iOS the first execution fails on first start, but only in the context of PersistQueryCache::add)
      db = connect()
      stmt.bind(params)
      const result = stmt.execute()
      console.warn(`First execution failed. Second try worked.`)
      return result
    }
    console.warn(`Failed to execute query: "${error}".`)
    throw error
  }
}

export const queryCache = new PersistQueryCache({
  add: (query: Query) => {
    const result = runStmt(selectStmt, [query.queryHash])

    const firstRow = result.rows?._array[0]
    if (firstRow != null) {
      try {
        query.state = JSON.parse(firstRow.queryState)
      } catch (error: unknown) {
        console.warn(`Failed to hydrate state for query "${query.queryHash}": ${error}`)
      }
    }

    runStmt(insertStmt, [query.queryHash, JSON.stringify(query.state)])
  },

  updated: (query: Query) => {
    runStmt(insertStmt, [query.queryHash, JSON.stringify(query.state)])
  },

  removed: (query: Query) => {
    runStmt(deleteStmt, [query.queryHash])
  },
})

How it works

TODO

License

MIT License.

Package Sidebar

Install

npm i react-query-cache-persistent

Weekly Downloads

110

Version

1.0.0

License

MIT

Unpacked Size

13.5 kB

Total Files

11

Last publish

Collaborators

  • patlux