Database Config
Most applications will only need the useLiveQuery
or useDocument
hooks, which use the default configuration. But if you need to do custom database setup, or configure a database name other than the React hook default ("useFireproof"
) you can call the useFireproof
hook directly. This is also useful if you want to distribute the Fireproof React hooks through your app with a React Context.
The main thing you'd use the top-level hook for is if you want to configure a custom database name, or pass a database instance instead of a string name. You can also see this option in use in the Create React App bundler workaround.
Top-level useFireproof Example
import { useFireproof } from 'use-fireproof';
function MyComponent() {
const {
database,
useLiveQuery,
useDocument
} = useFireproof(
"database-name" | databaseInstance
);
return (<div>
{/* ... your UI here ... */}
</div>)
}
The return value looks like { useLiveQuery, useDocument, database }
where the database
is the Fireproof instance that you can interact with using put
and get
, or via your indexes. The useLiveQuery
and useDocument
functions are configured versions of the top-level hooks and are the recommended API to update your React app in real-time.
Use Live Query
You can configure useLiveQuery
with a database name by instantiating the useFireproof
hook directly. Here's an example:
import { useFireproof } from 'use-fireproof';
export default TodoList = () => {
const { database, useLiveQuery } = useFireproof("my-todo-app")
const todos = useLiveQuery('date').docs
...
Use Document
You can configure useDocument
with a database name instantiating the useFireproof
hook directly. Here's an example:
import { useFireproof } from 'use-fireproof';
export default TodoList = () => {
const { useDocument } = useFireproof("my-todo-app")
const [todo, setTodo, saveTodo] = useDocument({title: 'New Todo'})
...
Database subscription
Changes made via remote sync peers, or other members of your cloud replica group will appear automatically if you use the useLiveQuery
and useDocument
APIs. This make writing collaborative workgroup software, and multiplayer games super easy. If you want to manage subscriptions to the database yourself, you can use the database.subscribe
function. This is useful if you want to manage your own state, or if you want to use the database API directly instead of the hooks.
Here is an example that uses direct database APIs instead of document and query hooks. You might see this in more complex applications that want to manage low-level details.
import { useFireproof } from 'use-fireproof';
function MyComponent() {
const { ready, database } = useFireproof("database-name");
// set a default empty document
const [doc, setDoc] = useState({});
// run the loader on first mount
useEffect(() => {
const getDataFn = async () => {
setDoc(await database.get('my-doc-id'));
};
getDataFn();
return database.subscribe(getDataFn);
}, [database]);
// a function to change the value of the document
const updateFn = async () => {
await database.put({ _id: 'my-doc-id', hello: 'world', updated_at: new Date() });
};
// render the document with a click handler to update it
return <pre onclick={updateFn}>{JSON.stringify(doc)}</pre>;
}
This results in a tiny application that updates the document when you click it. In a real application you'd probably query an index to present eg. all of the photos in a gallery.
Hint: Deterministic Fixtures
If you are running the same setup across multiple users installations, you probably want to use deterministic randomness to generate the same data on each run, so people can sync together. Here is an example of generating deterministic fixtures, using mulberry32
for deterministic randomness so re-runs give the same CID, avoiding unnecessary bloat at development time, taken from the TodoMVC demo app.
function mulberry32(a) {
return function () {
let t = (a += 0x6d2b79f5);
t = Math.imul(t ^ (t >>> 15), t | 1);
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
const rand = mulberry32(1); // determinstic fixtures
export default async function loadFixtures(database) {
const nextId = (prefix = '') => prefix + rand().toString(32).slice(2);
const listTitles = ['Building Apps', 'Having Fun', 'Getting Groceries'];
const todoTitles = [
[
'In the browser',
'On the phone',
'With or without Redux',
'Login components',
'GraphQL queries',
'Automatic replication and versioning',
],
['Rollerskating meetup', 'Motorcycle ride', 'Write a sci-fi story with ChatGPT'],
[
'Macadamia nut milk',
'Avocado toast',
'Coffee',
'Bacon',
'Sourdough bread',
'Fruit salad',
],
];
let ok;
for (let j = 0; j < 3; j++) {
ok = await database.put({
title: listTitles[j],
type: 'list',
_id: nextId('' + j),
});
for (let i = 0; i < todoTitles[j].length; i++) {
await database.put({
_id: nextId(),
title: todoTitles[j][i],
listId: ok.id,
completed: rand() > 0.75,
type: 'todo',
});
}
}
}