Ecosyste.ms: Awesome

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

Awesome Lists | Featured Topics | Projects

https://github.com/cvara/rxdb-hooks

React hooks for integrating with RxDB
https://github.com/cvara/rxdb-hooks

react react-hooks react-rxdb rxdb use-rxdb

Last synced: about 2 months ago
JSON representation

React hooks for integrating with RxDB

Awesome Lists containing this project

README

        

# rxdb-hooks

#### React hooks for integrating with [RxDB](https://github.com/pubkey/rxdb)


npm version






downloads

## Table of Contents

Click to expand

- [About](#about)
- [Installation](#installation)
- [Example](#example)
- [Compatibility with RxDB](#compatibility-with-rxdb)
- [Migration Guide](#migration-guide)
- [API](#api)
- [`Provider`](#provider)
- [`useRxDB`](#userxdb)
- [`useRxCollection`](#userxcollection)
- [`useRxQuery`](#userxquery)
- [`useRxData`](#userxdata)
- [`useRxDocument`](#userxdocument)
- [Recipes](#recipes)
- [Query and Query Constructor memoization](#query-and-query-constructor-memoization)
- [Lazy instantiation of RxDatabase & RxCollections](#lazy-instantiation-of-rxdatabase--rxcollections)
- [Mutations](#mutations)
- [LICENSE](#license)

## About

Nothing fancy, just conveniently handles common use cases such as:

- subscribing to query observables and translating results into React state
- cleaning up after subscriptions where necessary
- paginating results
- maintaining useful state information (i.e. data fetching or data exhaustion during pagination)
- lazily creating or destroying collections

## Installation

```bash
# using npm
npm install rxdb-hooks

# using yarn
yarn add rxdb-hooks
```

## Example

**Root.jsx**:

```javascript
import React, { useEffect } from 'react';
import { Provider } from 'rxdb-hooks';
import initialize from './initialize';

const Root = () => {
const [db, setDb] = useState();

useEffect(() => {
// RxDB instantiation can be asynchronous
initialize().then(setDb);
}, []);

// Until db becomes available, consumer hooks that
// depend on it will still work, absorbing the delay
// by setting their state to isFetching:true
return (



);
};
```

**Consumer.jsx**:

```javascript
import React from 'react';
import { useRxData } from 'rxdb-hooks';

const Consumer = () => {
const { result: characters, isFetching } = useRxData(
// the collection to be queried
'characters',
// a function returning the query to be applied
collection =>
collection.find({
selector: {
affiliation: 'jedi',
},
})
);

if (isFetching) {
return 'loading characters...';
}

return (


    {characters.map((character, idx) => (
  • {character.name}

  • ))}

);
};
```

**initialize.js**:

```javascript
const initialize = async () => {
// create RxDB
const db = await createRxDatabase({
name: 'test_database',
});

// create a collection
const collection = await db.addCollections({
characters: {
schema: {
title: 'characters',
version: 0,
type: 'object',
primaryKey: 'id',
properties: {
id: {
type: 'string',
maxLength: 100,
},
name: {
type: 'string',
},
},
},
},
});

// maybe sync collection to a remote
// ...

return db;
};
```

## Compatibility with RxDB

The core API of rxdb-hooks remains largely the same across all major versions _beyond_ `1.x`, however some parts of the internal
implementation (most notably [the plugin](src/plugins.ts)) differ based on the version of rxdb we need to target **\***.
Please use the appropriate version of rxdb-hooks as per this table:

| rxdb-hooks version | targeted RxDB version |
| ------------------ | ---------------------- |
| `5.x` | `14.x` |
| `4.1.x` | `13.x` |
| `4.0.x` | `10.x`, `11.x`, `12.x` |
| `3.x` | `9.x` |
| `1.x`, `2.x` | `8.x` |

_\* Versions 7.x of RxDB and below have not been tested and are not guaranteed to work with rxdb-hooks_

## Migration Guide

### `4.x` => `5.x`

- `useRxDocument` has been dropped; for fetching single documents simply use `useRxQuery` or `useRxData`
- observing lazily created collection has become an opt-in feature that, if needed, has to be explicitly enabled by using the provided plugin. For more info see [Lazy instantiation of RxDatabase & RxCollections](#lazy-instantiation-of-rxdatabase--rxcollections)

## API

### `Provider`

The `` makes the RxDatabase instance available to nested components and is required for all subsequent hooks to work.

#### Props

| Property | Type | Description |
| -------- | ------------ | -------------------------------------------- |
| `db` | `RxDatabase` | the RxDatabase instance to consume data from |


### `useRxDB`

Returns the RxDatabase instance made available by the ``

```javascript
function useRxDB(): RxDatabase
```

#### Example

```javascript
const db = useRxDB();
```


### `useRxCollection`

Given a collection name returns an RxCollection instance, if found in RxDatabase.

```javascript
function useRxCollection(name: string): RxCollection | null
```

#### Example

```javascript
const collection = useRxCollection('characters');
```


### `useRxQuery`

Subscribes to given RxQuery object providing query results and some helpful extra state variables.

```javascript
function useRxQuery(query: RxQuery, options?: UseRxQueryOptions): RxQueryResult
```

#### `options: UseRxQueryOptions`

| Option | Type | Description |
| ------------ | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pageSize` | `number` | (optional) enables pagination & defines page limit |
| `pagination` | `"Traditional" \| "Infinite"` | (optional) determines pagination mode:
`Traditional`: results are split into pages, starts by rendering the first page and total `pageCount` is returned, allowing for requesting results of any specific page.
`Infinite`: first page of results is rendered, allowing for gradually requesting more.
**Default**: `"Traditional"` |
| `json` | `boolean` | (optional) when `true` resulting documents will be converted to plain JavaScript objects; equivalent to manually calling `.toJSON()` on each `RxDocument`. **Default**: `false` |

#### `result: RxQueryResult`

| Property | Type | Description |
| ------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------- |
| `result` | `T[] \| RxDocument[]` | the resulting array of objects or `RxDocument` instances, depending on `json` option |
| `isFetching` | `boolean` | fetching state indicator |
| `currentPage` | `number` | relevant in **all** pagination modes; holds number of current page |
| `isExhausted` | `boolean` | relevant in **Infinite** pagination; flags result list as "exhausted", meaning all documents have been already fetched |
| `fetchMore` | `() => void` | relevant in **Infinite** pagination; a function to be called by the consumer to request documents of the next page |
| `resetList` | `() => void` | relevant in **Infinite** pagination; a function to be called by the consumer to reset paginated results |
| `pageCount` | `number` | relevant in **Traditional** pagination; holds the total number of pages available |
| `fetchPage` | `(page: number) => void` | relevant in **Traditional** pagination; a function to be called by the consumer to request results of a specific page |

#### Simple Example

```javascript
const collection = useRxCollection('characters');

const query = collection.find().where('affiliation').equals('Jedi');

const { result } = useRxQuery(query);
```

#### Infinite Scroll Pagination Example

```javascript
const collection = useRxCollection('characters');

const query = collection.find().where('affiliation').equals('Jedi');

const {
result: characters,
isFetching,
fetchMore,
isExhausted,
} = useRxQuery(query, {
pageSize: 5,
pagination: 'Infinite',
});

if (isFetching) {
return 'Loading...';
}

return (

{characters.map((character, index) => (

))}
{!isExhausted && load more}

);
```

#### Traditional Pagination Example

```javascript
const collection = useRxCollection('characters');

const query = collection.find({
selector: {
affiliation: 'Jedi',
},
});

const {
result: characters,
isFetching,
fetchPage,
pageCount,
} = useRxQuery(query, {
pageSize: 5,
pagination: 'Traditional',
});

if (isFetching) {
return 'Loading...';
}

// render results and leverage pageCount to render page navigation
return (



{characters.map((character, index) => (

))}


{Array(pageCount)
.fill()
.map((x, i) => (
{
fetchPage(i + 1);
}}
>
page {i + 1}

))}


);
```


### `useRxData`

Convenience wrapper around `useRxQuery` that expects a collection name & a query constructor function

```javascript
function useRxData(
collectionName: string,
queryConstructor: ((collection: RxCollection) => RxQuery | undefined) | undefined,
options?: UseRxQueryOptions
): RxQueryResult
```

#### Example

```javascript
const { result } = useRxData('characters', collection =>
collection.find().where('affiliation').equals('Jedi')
);
```


## Recipes

### Query and Query Constructor memoization

By design, `useRxQuery` will re-subscribe to `query` object whenever it changes, allowing
for query criteria to be modified during component updates. For this reason, to
avoid unnecessary re-subscriptions, query should be memoized (i.e. via react's `useMemo`):

```javascript
const { affiliation } = props;
const collection = useRxCollection('characters');

const query = useMemo(
() =>
collection.find({
selector: {
affiliation,
},
}),
[collection, affiliation]
);

const { result } = useRxQuery(query);
```

Same goes for `useRxData` and the `queryConstructor` function:

```javascript
const { affiliation } = props;

const queryConstructor = useCallback(
collection =>
collection.find({
selector: {
affiliation,
},
}),
[affiliation]
);

const { result } = useRxData('characters', queryConstructor);
```

### Lazy instantiation of RxDatabase & RxCollections

All rxdb-hooks give you the ability to lazily instantiate the database and the
collections within it. Initial delay until the above become available is absorbed
by indicating the state as fetching (`isFetching:true`).

Since `v5.0.0` of `rxdb-hooks`, observing newly created collections has become
an **opt-in** feature that, _if needed_, has to be enabled via the provided `observeNewCollections` plugin:

```javascript
import { addRxPlugin } from 'rxdb';
import { observeNewCollections } from 'rxdb-hooks';

addRxPlugin(observeNewCollections);
```

Adding the plugin makes it possible for all rxdb-hooks to pick up data from
collections that are lazily added after the inital db initialization.

Also note that lazily instantiating the rxdb instance itself is supported
out-of-the-box, **the plugin only affects lazy collection creation**.

### Mutations

Performing mutations on data is possible through the APIs provided by [RxDocument](https://rxdb.info/rx-document.html#functions)
and [RxCollection](https://rxdb.info/rx-collection.html#functions):

#### Example

```javascript
const collection = useRxCollection('characters');

collection.upsert({
name: 'Luke Skywalker',
affiliation: 'Jedi',
});
```

## LICENSE

MIT