Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/piotr-cz/swr-idb-cache

IndexedDB Cache Provider for SWR
https://github.com/piotr-cz/swr-idb-cache

Last synced: 12 days ago
JSON representation

IndexedDB Cache Provider for SWR

Awesome Lists containing this project

README

        

# IndexedDB Cache Provider for [SWR](https://swr.vercel.app/)

Synchronize SWR Cache with [IndexedDB](https://developer.mozilla.org/en-US/docs/Glossary/IndexedDB) to get offline cache.

Please note that the cache provider initialization process is asynchronous and until it's resolved, best practise is to render fallback component.

> [!WARNING]
> When passing secrets such as an API token to fetcher ([example](https://swr.vercel.app/docs/arguments)),
> it will be saved in the persistent cache as a part of key generated by [stable-hash](https://github.com/shuding/stable-hash).
>
> To overcome this, you may use custom comparision function in SWR Config that replaces secret by it's checksum.

## How does it work?

Library reads current state of cache stored in [IndexedDB](https://developer.mozilla.org/en-US/docs/Glossary/IndexedDB) into memory using [idb](https://github.com/jakearchibald/idb) during it's initialization.
Then it resolves into Cache Provider which should be passed to SWR.

Read SWR Docs > [Cache](https://swr.vercel.app/docs/advanced/cache) if your are interested in more information about implementation details.

## Installation

Using npm:

```console
npm install --save @piotr-cz/swr-idb-cache
```

or Yarn:

```console
yarn add @piotr-cz/swr-idb-cache

```

## Requirements

- [SWR](https://swr.vercel.app/) ^2.0.0
*Note: For SWR 1.x use the 1.0.0-rc.2 version of this package*
- Works with [React](https://reactjs.org/) ^16.11 and [Preact](https://preactjs.com/)

## Setup

Initialize library and when it's ready, pass it in configuration under `provider` key to SWR.

To wait until provider is resolved, use bundled `useCacheProvider` hook:

```jsx
// App.jsx
import { SWRConfig } from 'swr'
import { useCacheProvider } from '@piotr-cz/swr-idb-cache'

function App() {
// Initialize
const cacheProvider = useCacheProvider({
dbName: 'my-app',
storeName: 'swr-cache',
})

// Cache Provider is being initialized - render fallback component in the meantime
if (!cacheProvider) {
return

Initializing cache…

}

return (



)
}
```

…or library like [react-use-promise](https://github.com/bsonntag/react-use-promise):

```js
import createCacheProvider from '@piotr-cz/swr-idb-cache'
import usePromise from 'react-use-promise'

function App() {
// Initialize
const [ cacheProvider ] = usePromise(() => createCacheProvider({
dbName: 'my-app',
storeName: 'swr-cache',
}), [])

// …
}

```

## Configuration

- `dbName`: IndexedDB Database name
- `storeName`: IndexedDB Store name
- `storageHandler` (optional): Custom Storage handler, see [IStorageHandler interface](./src/types.ts#L31)
- `version` (optional): Schema version, defaults to `1`
- `onError` (optional): Database error handler, defaults to noop function

Note: When using `useCacheProvider`, changing options doesn't create new cache provider.

## Known issues

### InvalidStateError

```
InvalidStateError
Failed to execute 'transaction' on 'IDBDatabase': The database connection is closing.
```

See idb [Issue #229](https://github.com/jakearchibald/idb/issues/229)

## Recipes

### Delete cache entry

```jsx
import useSWR, { useSWRConfig } from 'swr'

export default function Item() {
const { data, error } = useSWR('/api/data')
const { cache } = useSWRConfig()

const handleRemove = () => {
// Remove from state
// …

// Remove from cache with key used in useSWR hook
cache.delete('/api/data')
}

return (

{/** Show item */}
{data &&

{data.label}


}

{/** Remove item */}

Remove


)
}
```

### Implement Garbage Collector

Define custom storage handler that extends timestamp storage

```js
// custom-storage-handler.js
import { timestampStorageHandler } from '@piotr-cz/swr-idb-cache'

// Define max age of 7 days
const maxAge = 7 * 24 * 60 * 60 * 1e3

const gcStorageHandler = {
...timestampStorageHandler,
// Revive each entry only when it's timestamp is newer than expiration
revive: (key, storeObject) =>
storeObject.ts > Date.now() - maxAge
// Unwrapped value
? timestampStorageHandler.revive(key, storeObject)
// Undefined to indicate item is stale
: undefined,
}

export default gcStorageHandler
```

Pass it to configuration

```diff
// App.jsx
import { SWRConfig } from 'swr'
import { useCacheProvider } from '@piotr-cz/swr-idb-cache'

+import customStorageHandler from './custom-storage-handler.js'
+
function App() {
// Initialize
const cacheProvider = useCacheProvider({
dbName: 'my-app',
storeName: 'swr-cache',
+ storageHandler: customStorageHandler,
})

// …
```

### Ignore API endpoints from cache persistence

Define custom storage handler that extends timestamp storage

```js
// custom-storage-handler.js
import { timestampStorageHandler } from '@piotr-cz/swr-idb-cache'

const blacklistStorageHandler = {
...timestampStorageHandler,
// Ignore entries fetched from API endpoints starting with /api/device
replace: (key, value) =>
!key.startsWith('/api/device/')
// Wrapped value
? timestampStorageHandler.replace(key, value)
// Undefined to ignore storing value
: undefined,
}

export default blacklistStorageHandler
```

Pass it in configuration as in the [recipe above](#implement-garbage-collector)