https://github.com/innei/jojoo
A utils and extra react hooks for Jotai v2.
https://github.com/innei/jojoo
Last synced: about 1 year ago
JSON representation
A utils and extra react hooks for Jotai v2.
- Host: GitHub
- URL: https://github.com/innei/jojoo
- Owner: Innei
- License: other
- Created: 2023-08-23T07:32:32.000Z (almost 3 years ago)
- Default Branch: master
- Last Pushed: 2024-07-08T10:03:50.000Z (almost 2 years ago)
- Last Synced: 2025-04-21T16:48:24.337Z (about 1 year ago)
- Language: TypeScript
- Homepage:
- Size: 673 KB
- Stars: 17
- Watchers: 2
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: readme.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# Jojoo
A utils and extra react hooks for Jotai v2.
## Install
```bash
pnpm i jojoo
```
## Usage
If you want to use custom store, should `setGlobalStore` first.
```ts
import { setGlobalStore } from 'jojoo'
import { createStore, getDefaultStore } from 'jotai/vanilla'
// if you use custom store
const store = createStore()
setGlobalStore(store)
```
### React Hooks
#### `createAtomsContext`
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](#createOverrideAtomsContext) for details.
Here is a simple example:
```tsx
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 (
)
}
const Count = () => {
const aCount = useStoreValue('aAtom')
return {aCount}
}
const IncrementButton = () => {
const inc = useContextActions().increment
return Plus
}
```
You can directly call `actions` returned by `createAtomsContext` outside of the component.
```tsx
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()
```
#### `createOverrideAtomsContext`
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:
```tsx
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 (
Data Id:
{id}
Data Text:
{text}
)
}
const DataActions: FC<> = (props) => {
const { testId } = props
const { setText } = useDataActions()
const text = useDataValue('text')
return (
setText(`${text} updated`)}>
)
}
// ReactNode structure like:
const App = () => (
)
```
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.
```jsx
// ReactNode structure like:
;
```
#### `createModelDataContext`
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:
```tsx
interface NoteModel {
title: string
}
const {
ModelDataProvider,
ModelDataAtomProvider,
getGlobalModelData: getModelData,
setGlobalModelData: setModelData,
useModelDataSelector,
useSetModelData,
} = createModelDataProvider()
export {
ModelDataProvider as CurrentNoteDataProvider,
ModelDataAtomProvider as CurrentNoteDataAtomProvider,
getModelData as getCurrentNoteData,
setModelData as setCurrentNoteData,
useModelDataSelector as useCurrentNoteDataSelector,
useSetModelData as useSetCurrentNoteData,
}
const App = () => {
return (
<>
>
)
}
const DataRender = () => {
const title = useCurrentNoteDataSelector((n) => n.title)
return {title}
}
```
You can also use `ModelDataAtomProvider` for scope isolation. In this way, the internal data of `ModelData` in both `App` and `AnotherData` are completely independent.
```tsx
const App = () => (
<>
>
)
const AnotherData = () => {
const overrideAtom = useMemo(() => atom(null as null | NoteModel), [])
return (
)
}
```
## License
2023 © Innei, Released under the MIT License.
> [Personal Website](https://innei.in/) · GitHub [@Innei](https://github.com/innei/)