useView
Overview
For accessing a map-reduce view use the useView
hook.
It also subscripts to updates of the view. With the added bonus of also subscribing to updates of the documents in the result. If you update or delete a document in a way that would remove it from the view, than it will be removed from the result of this hook.
If a design document isn't replicated yet, useView will error. But once it did sync, useView will query it. Same when the design document did change. Whenever it changes, useView will re-query.
Read more about views in CouchDB's Guide to Views.
useView
can only be invoked from a component nested inside of a <Provider />
.
useView
requirespouchdb-mapreduce
to be installed and setup. If you use thepouchdb
orpouchdb-browser
packages, it is already setup.
pouchdb-mapreduce
uses theFunction
constructor. If your Javascript-environment doesn't alloweval
, then you can't useuseView
!
Parameters
useView
has the same options as db.query()
. Options
descriptions are copied from the PouchDB API page.
fun: string | PouchDB.Map
- Map/reduce function, which can be one of the following:- The name of a view in an existing design document. It can be
'designDocName/viewName'
or'viewName'
as a shorthand for'viewName/viewName'
. - A map function. Danger! It will scan the entire database every time a parameter or document did change! Use only for development!
- A full CouchDB-style map/reduce view
{map: ..., reduce: ...}
. Danger! It will scan the entire database every time a parameter or document did change! Use only for development!
- The name of a view in an existing design document. It can be
options?: object
- An options object. It has the same options asdb.query()
.options.reduce?: boolean
- Should the result be reduced by the definedreduce
function? Defaults totrue
.true
- Return the result of the reduce function.false
- Don't use the reduce function.- It gets ignored if no reduce function is defined in the view.
options.include_docs?: boolean
- Include the document in each row in thedoc
field.options.conflicts?: boolean
- Ifoptions.include_docs
istrue
, include conflicts in the_conflicts
field of a doc.options.attachments?: boolean
- Ifoptions.include_docs
istrue
, include attachment data as base64-encoded string.options.binary?: boolean
- Ifoptions.include_docs
andoptions.attachments
aretrue
, return attachment data as Blobs, instead of as base64-encoded strings.options.startkey?: any
- Get documents with IDs in a certain range. The range starts with this key.options.endkey?: any
- Get documents with IDs in a certain range. The range ends with this key.options.inclusive_end?: boolean
- Include rows having a key equal to the givenoptions.endkey
. Default istrue
.options.limit?: number
- Maximum number of rows to return.options.skip?: number
- Number of rows to skip before returning (warning: poor performance).options.descending?: boolean
- Reverse the order of the output rows. Also reversoptions.startkey
andoptions.endkey
!options.key?: any
- Only return rows matching this key.options.keys?: any[]
- Array of keys to fetch in a single shout.- Neither
options.startkey
noroptions.endkey
can be specified with this option. - The rows are returned in the same order as the supplied
keys
array. - The row for a deleted document will have the revision ID of the deletion, and an extra key
"deleted": true
in thevalue
property. - The row for a nonexistent document will only contain an
"error"
property with the value"not_found"
.
- Neither
options.group?: boolean
- True if you want the reduce function to group results by keys, rather than returning a single result. Defaults tofalse
.options.group_level?: number
- Number of elements in a key to group by, assuming the keys are arrays. Defaults to the full length of the array.options.stale?: 'ok' | 'update_after'
- Only applies to saved views. Can be one of:- unspecified (default): Returns the latest results, waiting for the view to build if necessary.
'ok' | 'update_after'
: Returns results immediately, even if they’re out-of-date. But starts a new request after the first request did resolved.
options.update_seq?: boolean
- Include anupdate_seq
value indicating which sequence id of the underlying database the view reflects.options.db?: string
- Selects the database to be used. The database is selected by it's name/key. The special key"_default"
selects the default database. Defaults to"_default"
.
startkey
,endkey
,key
andkeys
are check for equality with a deep equal algorithm. And only if they differentiate by value will they cause a new query be made.
Result
useView
results an object with those fields:
rows: object[]
- Array of objects that contain the requested information. Empty during the first fetch or during an error. Each object has following fields:id?: string
-_id
of the document from which the row was mapped. If reduced this is not defined.key: any | null
- Key that was the first argument passed toemit
in themap
function.value: any
- Value of the row.- If no reduce was done: Value that was the second argument passed to
emit
in themap
function. - Else: Result of the reduce function.
- If no reduce was done: Value that was the second argument passed to
doc?: PouchDB.Core.Document
- Ifoptions.include_docs
wastrue
, this field will contain the document. And ifattachments
is alsotrue
, the document will contain the attachment data in the"_attachments"
field.
offset?: number
- Theskip
provided.total_rows?: number
- The total number of non-deleted documents in the database.update_seq?: number | string
- Ifupdate_seq
istrue
, this will contain the sequence id of the underlying database.state: 'loading' | 'done' | 'error'
- Current state of the hook.loading
- It is loading the documents. Or it is loading the updated version of them.done
- The documents are loaded, and no update is being loaded.error
- There was an error with fetching the documents. Look into theerror
field.
loading: boolean
- It is loading. The state isloading
. This is only a shorthand.error: PouchDB.Error | null
- If there was an error, then this field will contain the error. The error is reset tonull
once a fetch was successful.
Example Usage
useView
is for more complex secondary indexes and statistics.
Find by tags
If you use a tag system and want to list all documents with a tag, then use useView
.
var designDoc = {
_id: '_design/app',
views: {
tags: {
map: function tagsMap(doc) {
if (isArray(doc.tags)) {
doc.tags.forEach(function (tag) {
// emit tag as the key and the doc title as the value
emit(tag, doc.title)
})
}
}.toString(),
reduce: '_count',
},
},
}
import React from 'react'
import { useView } from 'use-pouchdb'
import { ErrorMessage } from './ErrorMessage'
export function ListAllOfTag({ tag }) {
const { rows, loading, error } = useView('app/tags', {
key: tag,
reduce: false, // don't reduce
})
if (loading && rows.length === 0) {
return <div>loading ...</div>
}
if (error) {
return <ErrorMessage error={error}>
}
return (
<ul>
{rows.map(row => ( // row.key is the tag and row.id is the _id of the document.
<li key={`${row.key} ${row.id}`}>{row.value}</li>
))}
</ul>
)
}
Get statistics
You can count things, or get other statistics.
This example counts the number of documents with a tag.
var designDoc = {
_id: '_design/app',
views: {
tags: {
map: function tagsMap(doc) {
if (isArray(doc.tags)) {
doc.tags.forEach(function (tag) {
// emit tag as the key and the doc title as the value
emit(tag, doc.title)
})
}
}.toString(),
reduce: '_count',
},
},
}
import React from 'react'
import { useView } from 'use-pouchdb'
import { ErrorMessage } from './ErrorMessage'
export function ListAllTags() {
const { rows, loading, error } = useView('app/tags', {
group: true,
// reduce: true // do reduce
})
if (loading && rows.length === 0) {
return <div>loading ...</div>
}
if (error) {
return <ErrorMessage error={error}>
}
return (
<ul>
{rows.map(row => (
<li key={row.key}>
Tag <strong>{row.key}</strong> - Number of documents: {row.value}
</li>
))}
</ul>
)
}
Booking statistics
You can count values together.
In this example you can sum the changes to a bank account in a time frame.
var designDoc = {
_id: '_design/accounting',
views: {
change: {
map: function accountChangeMap(doc) {
// only if it is the correct doc type
if (doc.type === 'booking') {
var time = new Date(doc.timestamp)
// use a complex key. Here an array
emit(
[
time.getUTCFullYear(),
time.getUTCMonth() + 1,
time.getUTCDate(),
time.getUTCHours(),
time.getUTCMinutes(),
time.getUTCSeconds(),
],
doc.amount
)
}
}.toString(),
reduce: '_sum',
},
},
}
import React from 'react'
import { useView } from 'use-pouchdb'
import { ErrorMessage } from './ErrorMessage'
export function BankAccountChange({ year }) {
const { rows, loading, error } = useView('accounting/change', {
group_level: 1, // sum together all keys with the same year
startkey: [year],
// objects are sorted last:
// https://docs.couchdb.org/en/stable/ddocs/views/collation.html#collation-specification
endkey: [year, {}],
// reduce: true // do reduce
})
if (loading && rows.length === 0) {
return <div>loading ...</div>
}
if (error) {
return <ErrorMessage error={error}>
}
return (
<p>
Your account did change {rows[0].value}€ in {year}.
</p>
)
}
Select a database
var designDoc = {
_id: '_design/app',
views: {
tags: {
map: function tagsMap(doc) {
if (isArray(doc.tags)) {
doc.tags.forEach(function (tag) {
// emit tag as the key and the doc title as the value
emit(tag, doc.title)
})
}
}.toString(),
reduce: '_count',
},
},
}
import React from 'react'
import { useView } from 'use-pouchdb'
import { ErrorMessage } from './ErrorMessage'
export function ListAllOfTag({ tag, isLocalReady }) {
const { rows, loading, error } = useView('app/tags', {
key: tag,
reduce: false, // don't reduce
// Select the database used
db: isLocalReady ? 'local' : 'remote'
})
if (loading && rows.length === 0) {
return <div>loading ...</div>
}
if (error) {
return <ErrorMessage error={error}>
}
return (
<ul>
{rows.map(row => ( // row.key is the tag and row.id is the _id of the document.
<li key={`${row.key} ${row.id}`}>{row.value}</li>
))}
</ul>
)
}