useFireproof hook for React
React hook to initialize a Fireproof database, automatically saving and loading the clock.
The hook takes two optional setup function arguments, defineDatabaseFn
and setupDatabaseFn
. See below for examples.
The return value looks like { ready, database, addSubscriber }
where the database
is your Fireproof instance that you can interact with using put
and get
, or via your indexes. The ready
flag turns true after setup completes, you can use this to activate your UI. The addSubscriber
function is used to update your app in realtime, see example.
Usage Example
In App.js:
import { FireproofCtx, useFireproof } from '@fireproof/core/hooks/use-fireproof'
function App() {
// establish the Fireproof context value
const fpCtxValue = useFireproof()
// render the rest of the application wrapped in the Fireproof provider
return (
<FireproofCtx.Provider value={fpCtxValue}>
<MyComponent />
</FireproofCtx.Provider>
)
}
In your components:
import { FireproofCtx } from '@fireproof/core/hooks/use-fireproof'
function MyComponent() {
// get Fireproof context
const { ready, database, addSubscriber } = useContext(FireproofCtx)
// set a default empty document
const [doc, setDoc] = useState({})
// function to load the document from the database
const getDataFn = async () => {
setDoc(await database.get("my-doc-id"))
}
// run that function when the database changes
addSubscriber('MyComponent', getDataFn)
// run the loader on first mount
useEffect(() => getDataFn(), [])
// 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 should result in a tiny application that updates the document when you click it. In a real appliction you'd probably query an index to present eg. all of the photos in a gallery.
Setup Functions
defineDatabaseFn
Synchronous function that defines the database, run this before any async calls. You can use it to do stuff like set up Indexes. Here's an example:
const defineIndexes = (database) => {
database.allLists = new Index(database, function (doc, map) {
if (doc.type === 'list') map(doc.type, doc)
})
database.todosByList = new Index(database, function (doc, map) {
if (doc.type === 'todo' && doc.listId) {
map([doc.listId, doc.createdAt], doc)
}
})
window.fireproof = database // 🤫 for dev
return database
}
setupDatabaseFn
Asynchronous function that uses the database when it's ready, run this to load fixture data, insert a dataset from somewhere else, etc. Here's a simple example:
async function setupDatabase(database)) {
const apiData = await (await fetch('https://dummyjson.com/products')).json()
for (const product of apiData.products) {
await database.put(product)
}
}
Note there are no protections against you running the same thing over and over again, so you probably want to put some logic in there to do the right thing.
Here is an example of generating deterministic fixtures, using mulberry32
for determinstic 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',
})
}
}
}