Skip to content

Jotai

Created: 2023-05-03 17:35:59 -0700 Modified: 2023-06-20 20:54:49 -0700

A read-only version of an atomFamily

Section titled A read-only version of an atomFamily
// This is a contrived interface just for the sake of example
interface Item {
id: number
name: string
}
function newItem(id: number): Item {
return {
id,
name: someFunctionToGetNameFromId(id)
}
}
const itemAtomFamily = atomFamily(
(id: number) => atom(newItem(id)),
(id1, id2) => id1 === id2
)
// By exporting only this, no one will have access to itemAtomFamily to be able to
// set items themselves
export const itemAtomFamilyReadOnly = atomFamily((id: number) =>
atom((get) => get(itemAtomFamily(id)))
)

Read-write version of the prior example

Section titled Read-write version of the prior example
export const itemAtomFamilyReadOnly = atomFamily((id: number) =>
atom(
(get) => get(itemAtomFamily(id)),
(get, set, name: string) => {
const item = get(itemAtomFamily(id))
set(itemAtomFamily(id), { ...item, name })
}
)
)

Get the next ID for some new object

Section titled Get the next ID for some new object
import { createStore } from "jotai"
const store = createStore()
const nextIdAtom = atom(0)
function getNextId(): number {
const nextId = store.get(nextIdAtom)
store.set(nextIdAtom, nextId + 1)
return nextId
}

See https://github.com/pmndrs/swc-jotai/issues/14.

As a workaround before realizing I’d forgotten to modify nextjs.config, I wrote a <JotaiUpdateDetector/> component. It just tells you when you’ve modified a file (or a dependency of a file) and should manually refresh.

store/User.ts
export const usersFileUpdatedAtom = atom(true)
JotaiUpdateDetector.tsx
import { usersFileUpdatedAtom } from "@/store/User"
import { postsFileUpdatedAtom } from "@/store/Posts"
import { PrimitiveAtom, useAtom } from "jotai"
import _ from "lodash"
import { useState, useEffect } from "react"
function useJotaiUpdateNotifier(atom: PrimitiveAtom<boolean>) {
const [alreadyDetectedUpdate, setAlreadyDetectedUpdate] = useState(false)
const [wasFileUpdated, setFileWasUpdated] = useAtom(atom)
const [needsRefresh, setNeedsRefresh] = useState(false)
useEffect(() => {
if (wasFileUpdated) {
if (alreadyDetectedUpdate) {
setNeedsRefresh(true)
}
setFileWasUpdated(false)
setAlreadyDetectedUpdate(true)
}
}, [
wasFileUpdated,
setFileWasUpdated,
alreadyDetectedUpdate,
setAlreadyDetectedUpdate,
setNeedsRefresh,
])
return needsRefresh
}
export function JotaiUpdateDetector() {
const [dismissed, setDismissed] = useState(false)
const atomFilePairs = [
{ file: "Users", atom: usersFileUpdatedAtom },
{ file: "Posts", atom: postsFileUpdatedAtom },
]
const messages = []
for (let i = 0; i < atomFilePairs.length; ++i) {
const { file, atom } = atomFilePairs[i]
// eslint-disable-next-line react-hooks/rules-of-hooks
if (useJotaiUpdateNotifier(atom)) {
messages.push(file)
}
}
if (_.isEmpty(messages) || dismissed) {
return null
}
return (
<div className="fixed flex p-4 text-4xl left-5 top-5 right-5 bg-red-900/75 text-white items-center justify-between">
⚠ These files were updated: {JSON.stringify(messages)}. You should
manually refresh the page.
<button onClick={() => setDismissed(true)}>
Dismiss
</button>
</div>
)
}