use-dexie
v1.2.4
Published
React Hooks to use Dexie.js IndexDB library with ease
Downloads
13
Maintainers
Readme
Dexie.js is a gorgeous library to use IndexDB with simple and powerful syntax. However, if you want to use Dexie.js within a React project you need to implement a series of logics that allow asynchronous interaction. For this purpose, useDexie is a library that includes a series of React Hooks that allow you to easily use IndexDB as a data source for React applications. In many cases useDexie allows you to completely replace status libraries such as Redux while ensuring a higher execution speed (data can be pre-loaded) and, of course, persistency.
The use-dexie hooks have been optimized to ensure:
- 🔥 Realtime Refresh of React elements that depend on a data source
- ♻️ Minimizing the number of refreshes to bare minimum
- ⚡️ Maximum Speed and Minimum memory footprint
- 🔦 Dynamic Query Composition
- 🕵🏻♂️ Performance and Stats Live monitoring
- Installation
- Basic Usage
- Hooks
- Query Syntax
- Example: To-Do-List with Create React App
Installation
npm install use-dexie
Basic Usage
Below is a simple example that shows how to instantiate useDexie, populate a simple database of Tasks and then query the database to receive the list of tasks and render them in the component. For a more detailed example see Example: To-Do-List Create React App.
import React from 'react';
import { useDexie, useDexieTable } from 'use-dexie';
function App() {
useDexie('TASKS_DB', { tasks: 'id, name, done' }, (db) => {
db.tasks.bulkPut([
{ id: 'T1', label: 'Learn useDexie', done: 'false' },
{ id: 'T2', label: 'Advanced useDexie', done: 'false' },
]);
});
const tasks = useDexieTable('tasks') || [];
return (
<div>
{tasks.map((task) => (
<span>task.label</span>
))}
</div>
);
}
Hooks
use-dexie offers a set of hooks to read, write and delete data from a single instance of IndexDB (it is not possible to use use-dexie on multiple databases at the same time. Solution: divide the data into several tables within the same DB). The first and most important hook to use is useDexie to initialize the database to be used.
useDexie
Object: db = useDexie(String: dbName, Object: [dbSchema], Number: dbVersion, function: [callback]);
useDexie is the main hook that should be invoked mandatorily as soon as possible inside your app to instantiate the DB you want to use. To instantiate the DB you need to provide the name, version and schema. The advice is to use useDexie in the root file of your React application to make the DB immediately available to all your components.
Params
| | Description | Example |
| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- |
| dbName | The name of DB (Mandatory) | "TASKS-DB" |
| dbSchema | The schema to create DB. I must follow the Dexie.js syntax | { tasks: 'id, name, done' }
|
| dbVersion | The version number of DB.Please note that if you want to change an already deployed DB you must change DB version (please check Dexie.js Documentation) | 1 |
| callback | useDexie returns the DB asynchronously as soon as it is instantiated. However, if you want to receive the DB synchronously so that you can update it, for example, you can specify a callback that will be called with the DB as soon as it is available. | See example below |
Example DB Initialize and Populate
useDexie('TASKS_DB', { tasks: 'id, name, done' }, (db) => {
db.tasks.bulkPut([
{ id: 'T1', label: 'Learn useDexie', done: 'false' },
{ id: 'T2', label: 'Advanced useDexie', done: 'false' },
]);
});
useDexieTable
Array: results = useDexieTable((String: tableName), (Object: [query]), (Function: [callback]));
useDexieTable is a hook to read data contained in a database table. It can be used in several ways:
- to receive all entries of a table
- to receive only one set of results depending on a query
- as a selector to select and process table content
useDexieTable can be used asynchronously by returning the result when available, or a callback can be provided to process the result inline.
Params
| | Description | Example | | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | | tableName | The name of the table to operate on | "tasks" | | query | Optional parameter to fetch data using useDexie [Query Syntax] (#query-syntax) | { where:[{ field: 'done', operator: 'equals', value: 'true' }]} | | callback | On optional callback to retrieve and process query/table results. Da notare che il risultato della callback viene restituito dalla funzione useDexieTable dando la possibilità di utilizzarla come un selector. | (results) => doSomething(results) |
Example Fetch entire Table
const task = useDexieTable('tasks');
Example Query Table
const task = useDexieTable('tasks', {
where: [{ field: 'done', operator: 'equals', value: 'true' }],
});
Optimized version using useMemo:
//⚠️ Wrapping WhereClause in useMemo reduce updates
const task = useDexieTable(
'tasks',
useMemo(() => ({ where: [{ field: 'done', operator: 'equals', value: 'true' }] }), [])
);
Example Fetch Table with selector Callback
const taskDone = useDexieTable('tasks', {
where: [
{ field: 'done', operator: 'equals', value: 'true' },
(results) => {
return results.map((result) => {
result.lastFetched = new Date();
return result;
});
},
],
});
Optimized version using useMemo and useCallback:
//⚠️ Wrapping WhereClause in useMemo and Callback in useCallback reduce updates
const taskDone = useDexieTable('tasks', useMemo(() => ({ where: [{ field: 'done', operator: 'equals', value: 'true' }] }), []),
useCallback((results) => {
return results.map((result) => {
result.lastFetched = new Date();
return result;
});
},[]),
],
});
⚠️ Consider it a good (and suggested) practice to use useMemo to wrap WhereClause objects and useCallback for callbacks to optimize performance and reduce React updates.
useDexieGetTable
Function: getFunc = useDexieGetTable((String: [tableName]), (Object: [query]));
useDexieGetTable is a hook that returns a function that can be used within a component's logic to asynchronously read table data. The main difference between useDexieTable and useDexieGetTable is that useDexieGetTable always returns a function that can get the results of a table and not the results of the table.
Params
| | Description | Example | | --------- | ------------------------------------------------------------------------------ | --------------------------------------------------------------- | | tableName | Optional parameter to specify the name of the table to operate on | "tasks" | | query | Optional parameter to fetch data using useDexie [Query Syntax] (#query-syntax) | { where:[{ field: 'done', operator: 'equals', value: 'true' }]} |
The callback function returned by useDexieGetTable can be used within the business logic of a component with the following syntax (the parameters are the same as for the useDexieTable function):
Array: results = getFunc((String: [tableName]), (Object: [query]), (Function: [callback]));
Callback Example
const [uncompleted, setUncompleted] = useState([]);
const getTasks = useDexieGetTable('tasks');
// Some Component Business Logic
const refreshUncompleted = useCallback(() => {
getTasks({ where: [{ field: 'done', operator: 'equals', value: 'false' }] }, (tasks) => {
setUncompleted(task);
});
}, [getTasks]);
// Render a Button to refresh uncompleted task state
return <Button onClick={refreshUncompleted}>Refresh</Button>;
useDexieObj
Object: results = useDexieObj((String: tableName), (Object: [query]), (Function: [callback]),(String: [idFieldName = id]));
The operation is the same as useDexieTable as the parameters. The difference is that the hook returns a javascript object.
You can optionally specify as string type parameter the name of the field you want to use to generate the keys of the object (default: "id").
Example
const taskObj = useDexieObj('task');
/* Returns
{
T1:{id: 'T1', label: 'Learn useDexie', done: 'false' },
T2:{id: 'T2', label: 'Advanced useDexie', done: 'false'}
}
*/
return <span>{taskObk.T1.label}</span>;
Using useDexieObj as selector:
const T1 = useDexieObj('task', (task) => task.T1);
return <span>{T1.label}</span>;
useDexieMap
Map: results = useDexieMap((String: tableName), (Object: [query]), (Function: [callback]));
The operation is the same as useDexieTable as the parameters. The difference is that the hook returns a javascript Map.
You can optionally specify as string type parameter the name of the field you want to use to generate the keys of the Map (default: "id").
Example
const taskMap = useDexieMap('task');
/* Returns
[
["T1",{id: 'T1', label: 'Learn useDexie', done: 'false' }],
["T2",{id: 'T2', label: 'Advanced useDexie', done: 'false'} ]
]
*/
return <span>{taskMap.get('T1').label}</span>;
Using useDexieMap as selector:
const T1 = useDexieMap('task', (task) => task.get('T1'));
return <span>{T1.label}</span>;
useDexieSet
Set: results = useDexieSet((String: tableName), (Object: [query]), (Function: [callback]));
The operation is the same as useDexieTable as the parameters. The difference is that the hook returns a javascript Set.
You can optionally specify as string type parameter the name of the field you want to use to generate the keys of the Set (default: "id").
Example
const taskSet = useDexieSet('task');
/* Returns
{
T1,
T2
}
*/
return <span>{taskSet.as('T1') ? 'Task T1 is here' : 'no T1 in task list'}</span>;
useDexieGetItem
Object: results || Function: callback = useDexieGetItem((String: tableName), (String: [itemId]), (String: [idField]));
useDexieGetItem is a versatile hook that aims to return an object in a table. It can be used in two different ways: "direct" or "deferred" mode as detailed below.
Params
| | Description | Example | | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | tableName | The name of the table to operate on | "tasks" | | itemId | The value of the key you want to find. It is of course possible to search for items only through the (or one of) primary keys that you have set in the database schema. The default key that is used by useDexieGetItem is "id". | "T1" | | idField | If you want to operate useDexieGetItem on another primary key than the default one ("id") you can use this parameter to specify the name of the key. | "id" |
If it is used in "deferred" mode and therefore if you do not directly specify the itemId useDexieGetItem returns a callback:
function((String: itemId), (Function: [callback]));
Callback Params
| | Description | Example | | -------- | --------------------------------------------------- | ------- | | itemId | The value of the key you want to find. | "T1" | | callback | An optional callback that returns the searched item | "id" |
Example: Direct
The first way is to specify the Id of the content directly in the invocation phase. In this way useDexieGetItem returns the object with the key specified in the way and therefore as soon as it is available:
// The default field where the search is performed is "id"
const task = useDexieGetItem('task', 'T1');
Example: Callback
The second way is to specify only the name of the table you want to operate on. In this way useDexieGetItem returns a callback function that can be used within the business logic of the React component.
const [default, setDefault] = useState();
const getTask = useDexieGetItem('task');
// Synchronous mode: the result could be undefined on the first invocation
const getTaskById = useCallback((id) => {
const task = getTask(id);
return task || {};
}, []);
// Asynchronous mode: provide a callback to get the result as soon as available
const setDefaultTask = useCallback((id) => {
getTask(id, (task) => {
setDefault(task);
});
}, []);
useDexieGetItemKey
Function: callback = useDexieGetItemKey((String: tableName));
This hook allows you to search for an item in the database and to have its Primary Key as return value.
Params
| | Description | Example | | --------- | ----------------------------------- | ------- | | tableName | The name of the table to operate on | "tasks" |
It returns a callback that can be used within the business logic of the React component.
function((Object: query), (Function: [callback]));
Callback Params
| | Description | Example | | -------- | --------------------------------------------------- | ------- | | query | The value of the key you want to find. | "T1" | | callback | An optional callback that returns the searched item | "id" |
Example
// The primary key (++) is an auto-generated and auto-incremented id
useDexie('BOOKS_DB', { books: '++, ISBN, title' }, (db) => {
db.tasks.bulkPut([
{ id: '1', ISBN: 'ABCD1234', title: 'Master React' },
{ id: '2', ISBN: 'EFGH5678', title: 'React for Dummies' },
]);
});
const getBookId = useDexieGetItemKey('books');
const getIdByISBN = usecallback((ISBN) => {
const id = getBookId({ where: [{ field: 'ISBN', operato: 'equals', value: ISBN }] });
return id;
}, []);
useDexiePutItem
Function: callback = useDexiePutItem((String: tableName));
This hook allows you to insert an item into a database table. The operation is performed in "upsert" mode i.e. if the item does not exist it is created, if an item with the same primary key already exists it is updated.
Params
| | Description | Example | | --------- | ----------------------------------- | ------- | | tableName | The name of the table to operate on | "tasks" |
It returns a callback that can be used within the business logic of the React component.
function((Object: item), (Function: [callback]));
Callback Params
| | Description | Example | | -------- | -------------------------------------------------------- | ---------------------------------------------- | | item | The item you want to insert | { id: 'T3', label: 'Have fun', done: 'false' } | | callback | An optional callback that returns when operation is done | () => alert("All Done!") |
Example
const putTask = useDexiePutItem('task');
const handleUpdateTask = useCallback((task) => {
putTask(task, () => {
alert(`Task ${task.id} updated!`);
});
}, []);
useDexiePutItems
Function: callback = useDexiePutItem((String: tableName));
This hook allows you to insert multiple items into a database table. The operation is performed in "upsert" mode i.e. if an item does not exist it is created, if an item with the same primary key already exists it is updated.
Params
| | Description | Example | | --------- | ----------------------------------- | ------- | | tableName | The name of the table to operate on | "tasks" |
It returns a callback that can be used within the business logic of the React component.
function((Array: items), (Function: [callback]));
Callback Params
| | Description | Example | | -------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | | items | The Array of items you want to insert | [{ id: 'T3', label: 'Have fun', done: 'false' },{ id: 'T4', label: 'Play Hard', done: 'false' }] | | callback | An optional callback that returns when operation is done | () => alert("All Done!") |
Example
const putTasks = useDexiePutItems('task');
const handleUpdateTasks = useCallback((tasks) => {
putTasks(tasks, () => {
alert(`Tasks: ${tasks.map((t) => t.id).join(',')} updated!`);
});
}, []);
useDexieUpdateItem
Function: callback = useDexieUpdateItem((String: tableName));
This hook is very convenient as a shortcut to take an item from a table, modify it and then update it without having to use the two hooks useDexieGetItem and useDexiePutItem.
Params
| | Description | Example | | --------- | ----------------------------------- | ------- | | tableName | The name of the table to operate on | "tasks" |
It returns a callback that can be used within the business logic of the React component.
function((Object: query), (Function: [callback]));
Callback Params
| | Description | Example |
| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
| query | WhereClause Object to get the item you want to modify. WhereClause must be in useDexie [Query Syntax] (#query-syntax) | { where:[{ field: 'id', operator: 'equals', value: 'T1' }]}
|
| callback | As a second parameter you can pass a callback function that will be invoked by passing the item found in the table. This function, once the item has been modified, must return it as a return value so that it can be updated in the database. | (item) => { item.done = "true"; return item;}
|
Example
const updateTask = useDexieUpdateItem('task');
const taskUpdate = useCallback(
(id) => {
updateTask({ where: [{ field: 'id', operator: 'equals', value: id }] }, (task) => {
task.done = 'true';
return task;
});
},
[updateTask]
);
useDexieDeleteItem
Function: callback = useDexiePutItem((String: tableName));
This hook allows you to delete an item from a database table.
Params
| | Description | Example | | --------- | ----------------------------------- | ------- | | tableName | The name of the table to operate on | "tasks" |
It returns a callback that can be used within the business logic of the React component.
function((Object: item), (Function: [callback]));
Callback Params
| | Description | Example | | -------- | -------------------------------------------------------- | ------------------------ | | item | The item you want to delete | "T1" | | callback | An optional callback that returns when operation is done | () => alert("All Done!") |
Example
const deleteTask = useDexieDeleteItem('task');
const handleDeleteTask = useCallback((task) => {
deleteTask(task, () => {
alert(`Task ${task.id} deleted!`);
});
}, []);
useDexieDeleteByQuery
Function: callback = useDexieDeleteByQuery((String: tableName));
This hook allows you to delete multiple items simultaneously from a database table.
Params
| | Description | Example | | --------- | ----------------------------------- | ------- | | tableName | The name of the table to operate on | "tasks" |
It returns a callback that can be used within the business logic of the React component.
function((Object: query), (Function: [callback]));
Callback Params
| | Description | Example |
| -------- | --------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
| query | WhereClause Object to get the item you want to delete. WhereClause must be in useDexie [Query Syntax] (#query-syntax) | { where:[{ field: 'done', operator: 'equals', value: 'true' }]}
|
| callback | As a second parameter you can pass a callback function that will be invoked when the delete operation is done. | () => alert("All Done Deleted!");
|
Example
const deleteByQuery = useDexieDeleteByQuery('task');
const deleteDoneTasks = useCallback(() => {
deleteByQuery({ where: [{ field: 'done', operator: 'equals', value: 'true' }] });
}, [updateTask]);
return <button onClick={deleteDoneTasks}>Delete Done</button>;
useDexieMonitor
This hook is designed exclusively for Development mode and provides a set of statistical data on database usage at a set frequency. It is useful if you want to implement a small component to monitor the usage and performance of the database during the development phase.
Object: data = useDexieMonitor((Number: refresh || null));
Example
// DEMO COMPONENT TO VISUALIZE useDexieMonitor
const devMode = process.env.NODE_ENV === 'development';
export default function Monitor(props) {
const data = useDexieMonitor(devMode ? 500 : null); // if you pass null monitor don't start, otherwise it refresh at freq you passed (in millisecs)
if (!devMode) return null;
return (
<div className={styles.container}>
{Object.entries(data).map(([key, values]) => {
const value = Array.isArray(values) ? values.join(', ') : values;
return (
<span className={styles.entry}>
<span className={styles.key}>{key}</span>
<span className={styles.value}>{value}</span>
</span>
);
})}
</div>
);
}
/*
[Data returned by useDexieMonitor every freq]
{
"subscriptions": 2, // number of tables where live refresh is active
"tables": [ // array of tables where live refresh is active
"settings",
"lists"
],
"subscribers": 3, // number of React components subscribed to live refresh
"maxActive": 3, // max number of active concurrent transactions to db
"active": 0, // current active transactions to db
"fulfilled": 3, // total number of transactions to db
"avg": "754.33", // average response time of transactions
"avgLast10": "754.33" // average last 10 response time of transactions
}
*/
Query Syntax
useDexie uses a simplified syntax to build queries for the DB. The syntax allows you to create multiple And and Or conditions, following the SQL logic as much as possible.
Where Clause
Compose a WhereClause object is the primary solution to obtain a subset of data from a table. The allowed operators for building queries are those provided by the Dexie.js library WhereClause.
{
where: [{ field: 'done', operator: 'equals', value: 'true' }],
}
The WhereClause object can be dynamically composed, and can be wrapped using React useMemo to optimize performance by reducing updates. If during the composition of the WhereClause object you want to "disable" the filtering operation, just return a null value to get all the records of the table.
const [filter, setFilter] = useState();
const tasks = useDexieTable(
'tasks',
useMemo(() => {
if (!filter) return null;
return {
where: [{ field: 'done', operator: 'equals', value: filter }],
};
}, [filter])
);
Or Clause
It is possible to set queries with a main where clause and a series of clauses in Or:
{
where: [
{
field: 'done',
operator: 'equals',
value: 'true',
or: [{ field: 'id', operator: 'oneOf', value: ['T1', 'T2'] }],
},
];
}
Alternatively you can set a query with a series of clauses in Or between them:
{
or: [
{ field: 'done', operator: 'equals', value: 'true' },
{ field: 'id', operator: 'oneOf', value: ["T1","T2"] }
],
}
And Clause
Given the nature of IndexDB and the way Dexie.js works, you can set queries in AND by adding filters with the following syntax:
{
where: [
{
field: 'done',
operator: 'equals',
value: 'true',
and: [{ filter: '(obj) => param.includes(obj.id)', param: ['T1', 'T2'] }],
},
];
}
Alternatively you can use an arrow function as filter
{
where: [
{
field: 'done',
operator: 'equals',
value: 'true',
and: [{ filter: (obj) => ['T1', 'T2'].includes(obj.id) }],
},
];
}
Filtering
Alternatively to the composition of a WhereClause you can directly specify a single function to filter the contents of a table you want to obtain. This solution is handy if you want to sort content rusltes. The orderBy option can only be activated with filtering and not by specifying a where clause.
{
filter: (obj) => ['T1', 'T2'].includes(obj.id);
}
Order By
The orderBy option can only be activated with filtering and not by specifying a where clause.
{
filter: (obj) => ['T1', 'T2'].includes(obj.id);
orderBy: 'name';
}
Pagination
{
where: [{ field: 'done', operator: 'equals', value: 'true' }],
offset: 0,
limit: 1000
}
Count Results
{
where: [{ field: 'done', operator: 'equals', value: 'true' }],
count: true
}
Example: To-Do-List with Create React App
Below is a simple example of a Create-React-App that implements a to-do list using all available hooks of use-dexie. Of course the use of some features in the example makes no practical sense except to show in practice the use of hooks.
import React, { useState, useCallback } from 'react';
import {
useDexie,
useDexieTable,
useDexiePutItem,
useDexieDeleteItem,
useDexieDeleteByQuery,
useDexieGetItem,
useDexieObj,
useDexieSet,
useDexieUpdateItem,
useDexieGetTable,
} from 'use-dexie';
import './App.css';
function App() {
useDexie('TASKS_DB', { tasks: 'id, name, done' }, (db) => {
db.tasks.bulkPut([
{ id: 'T1', label: 'Learn useDexie', done: 'false' },
{ id: 'T2', label: 'Advanced useDexie', done: 'false' },
]);
});
const updateTask = useDexiePutItem('tasks');
const deepUpdateTask = useDexieUpdateItem('tasks');
const deleteTask = useDexieDeleteItem('tasks');
const deleteByQuery = useDexieDeleteByQuery('tasks');
const getTask = useDexieGetItem('tasks');
const allTask = useDexieObj('tasks');
const completedTask = useDexieSet(
'tasks',
{
where: [{ field: 'done', operator: 'equals', value: 'true' }],
},
'label'
);
const tasks = useDexieTable('tasks') || [];
const getTasks = useDexieGetTable('tasks');
const [task, setTask] = useState();
const getTaskInfo = useCallback(
(id) => {
getTask(id, (task) => alert(JSON.stringify(task, null, 1)));
},
[getTask]
);
const deepTaskUpdate = useCallback(() => {
const id = 'T1';
deepUpdateTask({ where: [{ field: 'id', operator: 'equals', value: id }] }, (task) => {
task.done = 'true';
return task;
});
}, [deepUpdateTask]);
const getUncompleted = useCallback(() => {
getTasks({ where: [{ field: 'done', operator: 'equals', value: 'false' }] }, (tasks) => {
alert(JSON.stringify(tasks, null, 1));
});
}, [getTasks]);
return (
<div className="App">
<table border="1">
<thead>
<tr>
<th>#</th>
<th>Label</th>
<th>
<button
onClick={(e) =>
deleteByQuery({ where: [{ field: 'done', operator: 'equals', value: 'true' }] })
}
>
Delete Competed
</button>
</th>
</tr>
</thead>
<tbody>
{tasks.map((task) => {
return (
<tr key={task.id}>
<td>
<input
type="checkbox"
checked={task.done === 'true' ? true : false}
onChange={() => {
updateTask({ ...task, done: task.done === 'true' ? 'false' : 'true' });
}}
/>
</td>
<td>
<a
href={task.id}
onClick={(e) => {
e.preventDefault();
getTaskInfo(task.id);
}}
>
<span style={{ textDecoration: task.done === 'true' ? 'line-through' : '' }}>
{task.label}
</span>
</a>
</td>
<td>
<button onClick={(e) => deleteTask(task.id)}>Delete</button>
</td>
</tr>
);
})}
<tr>
<td colSpan="3">
<input type="text" onChange={(e) => setTask(e.target.value)} value={task || ''} />
<button
onClick={(e) => {
if (task) {
updateTask({ id: new Date().getTime(), label: task, done: false });
setTask('');
}
}}
>
ADD
</button>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>
<button onClick={(e) => alert(JSON.stringify(allTask, null, 1))}>All Tasks</button>
</td>
<td>
<button onClick={(e) => alert([...completedTask.keys()])}>Completed Tasks</button>
</td>
<td>
<button onClick={(e) => getUncompleted()}>Uncompleted Tasks</button>
</td>
</tr>
<tr>
<td colSpan="3">
<button onClick={(e) => deepTaskUpdate()}>Complete T1</button>
</td>
</tr>
</tfoot>
</table>
</div>
);
}
export default App;