A utils and extra react hooks for Jotai v2.
pnpm i jojoo
If you want to use custom store, should setGlobalStore
first.
import { setGlobalStore } from 'jojoo'
import { createStore, getDefaultStore } from 'jotai/vanilla'
// if you use custom store
const store = createStore()
setGlobalStore(store)
You can use createAtomsContext
to implement a simple Store. Pass in an object of atoms as state. An optional second argument is an action that accepts a context object. You can access the current scope's atoms through ctx.atoms
and then change the value of an atom in the current context using set
.
Note
The atoms
may not be the passed-in globalAtoms
; they might be atoms that are overridden by a Provider
. See createOverrideAtomsContext for details.
Here is a simple example:
import 'jojoo/react'
const globalAtoms = {
aAtom: atom(0),
bAtom: atom(false),
}
const context = createAtomsContext(globalAtoms, (ctx) => {
const { atoms, set } = ctx
return {
increment: () => {
set(atoms.aAtom, (current) => current + 1)
},
}
})
const [Provider, hooks, atoms, actions] = context
const [useContextAtoms, useStoreValue, useContextActions] = hooks
// Wrap `Provider` for your component
const App = () => {
return (
<Provider>
<Count />
<IncrementButton />
</Provider>
)
}
const Count = () => {
const aCount = useStoreValue('aAtom')
return <span>{aCount}</span>
}
const IncrementButton = () => {
const inc = useContextActions().increment
return <button onClick={inc}>Plus</button>
}
You can directly call actions
returned by createAtomsContext
outside of the component.
const context = createAtomsContext(globalAtoms, (ctx) => {
const { atoms, set } = ctx
return {
increment: () => {
set(atoms.aAtom, (current) => current + 1)
},
}
})
const [Provider, hooks, atoms, actions] = context
// not in React component.
// do something..
actions.increment()
In some cases, atoms created via createAtomsContext
are used to manage data in a global store, which might be a global post state manager. However, when there exists a nested post on the page, you can't manage them globally.
At this point, you can use createOverrideAtomsContext
to isolate global states, enabling state isolation between child components.
Here is a simple example:
const context = createAtomsContext(
{
postId: atom('0'),
text: atom('global post text'),
},
({ atoms, set }) => {
return {
setText: (text: string) => {
set(atoms.text, text)
},
}
},
)
const [GlobalDataProvider, [useAtoms, useDataValue, useDataActions]] = context
const OverrideProvider = createOverrideAtomsContext(context, {
text: atom('override post text'),
postId: atom('1'),
})
const DataRender: FC<{}> = ({ testId }) => {
const text = useDataValue('text')
const id = useDataValue('postId')
return (
<div>
<p>
Data Id:
<span>{id}</span>
</p>
<p>
Data Text:
<span>{text}</span>
</p>
</div>
)
}
const DataActions: FC<> = (props) => {
const { testId } = props
const { setText } = useDataActions()
const text = useDataValue('text')
return (
<div>
<button onClick={() => setText(`${text} updated`)}></button>
</div>
)
}
// ReactNode structure like:
const App = () => (
<GlobalDataProvider>
<DataRender />
<DataActions />
<OverrideProvider>
<DataRender />
<DataActions />
</OverrideProvider>
</GlobalDataProvider>
)
Child components wrapped by OverrideProvider
will use the overridden atoms, isolated from global atoms. Of course, you can also use it in a nested manner.
// ReactNode structure like:
;<GlobalDataProvider>
<DataRender />
<DataActions />
<OverrideProvider>
<DataRender />
<DataActions />
<OverrideProvider>
<DataRender />
<DataActions />
</OverrideProvider>
</OverrideProvider>
</GlobalDataProvider>
Create a dataset context through createModelDataContext
, which can manage data with Jotai, and then pass it to descendants through React.context. Utilize the feature of React.context to isolate state in multiple scenarios.
A simple usage example:
interface NoteModel {
title: string
}
const {
ModelDataProvider,
ModelDataAtomProvider,
getGlobalModelData: getModelData,
setGlobalModelData: setModelData,
useModelDataSelector,
useSetModelData,
} = createModelDataProvider<NoteModel>()
export {
ModelDataProvider as CurrentNoteDataProvider,
ModelDataAtomProvider as CurrentNoteDataAtomProvider,
getModelData as getCurrentNoteData,
setModelData as setCurrentNoteData,
useModelDataSelector as useCurrentNoteDataSelector,
useSetModelData as useSetCurrentNoteData,
}
const App = () => {
return (
<>
<CurrentNoteDataProvider data={data} />
<DataRender />
</>
)
}
const DataRender = () => {
const title = useCurrentNoteDataSelector((n) => n.title)
return <span>{title}</span>
}
You can also use ModelDataAtomProvider
for scope isolation. In this way, the internal data of ModelData
in both App
and AnotherData
are completely independent.
const App = () => (
<>
<CurrentNoteDataProvider data={data} />
<DataRender />
<AnotherData />
</>
)
const AnotherData = () => {
const overrideAtom = useMemo(() => atom(null as null | NoteModel), [])
return (
<CurrentNoteDataAtomProvider overrideAtom={overrideAtom}>
<CurrentNoteDataProvider data={data} />
<DataRender />
</CurrentNoteDataAtomProvider>
)
}
2023 © Innei, Released under the MIT License.
Personal Website · GitHub @Innei