Skip to content

Commit 230e7e9

Browse files
authored
docs: add docs for bunja (#2938)
* docs: add bunja example * docs: move bunja from scope to third-party * docs: simplified example of a bunja depending on another bunja
1 parent aef669d commit 230e7e9

File tree

1 file changed

+208
-0
lines changed

1 file changed

+208
-0
lines changed

docs/third-party/bunja.mdx

+208
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
---
2+
title: Bunja
3+
description: State Lifetime Manager
4+
nav: 5.03
5+
keywords: scope,di,raii,lifetime
6+
---
7+
8+
[Bunja](https://github.com/disjukr/bunja) is lightweight State Lifetime Manager.
9+
10+
It provides an RAII wrapper for jōtai atoms.
11+
12+
---
13+
14+
See also:
15+
16+
- [Bunja README](https://github.com/disjukr/bunja/blob/main/README.md)
17+
- [Presentations](https://github.com/disjukr/bunja/tree/main/presentations)
18+
19+
## install
20+
21+
```
22+
npm install bunja
23+
```
24+
25+
### Defining a Bunja
26+
27+
You can define a bunja using the `bunja` function.
28+
29+
When you access the defined bunja with the `useBunja` hook, a bunja instance is created.
30+
31+
If all components in the render tree that refer to the bunja disappear, the bunja instance is automatically destroyed.
32+
33+
If you want to trigger effects when the lifetime of a bunja starts and ends, you can use the `bunja.effect` field.
34+
35+
```ts
36+
import { bunja } from 'bunja'
37+
import { useBunja } from 'bunja/react'
38+
39+
const countBunja = bunja([], () => {
40+
const countAtom = atom(0)
41+
return {
42+
countAtom,
43+
[bunja.effect]() {
44+
console.log('mounted')
45+
return () => console.log('unmounted')
46+
},
47+
}
48+
})
49+
50+
function MyComponent() {
51+
const { countAtom } = useBunja(countBunja)
52+
const [count, setCount] = useAtom(countAtom)
53+
// Your component logic here
54+
}
55+
```
56+
57+
### Defining a Bunja that relies on other Bunja
58+
59+
If you want to manage a state with a broad lifetime and another state with a narrower lifetime, you can create a (narrower) bunja that depends on a (broader) bunja.
60+
61+
For example, you can think of a bunja that holds the page state and another bunja that holds the modal state.
62+
63+
The page state lives longer than the modal state, and the modal state should exist from the moment the modal opens until it closes.
64+
65+
In such a case, you can write the following code.
66+
67+
```tsx
68+
const pageBunja = bunja([], () => {
69+
const pageStateAtom = atom({})
70+
return { pageStateAtom }
71+
})
72+
73+
const childBunja = bunja([pageBunja], ({ pageStateAtom }) => {
74+
const childStateAtom = atom((get) => ({
75+
...get(pageStateAtom),
76+
child: 'state',
77+
}))
78+
return { childStateAtom }
79+
})
80+
81+
const modalBunja = bunja([pageBunja], ({ pageStateAtom }) => {
82+
const modalStateAtom = atom((get) => ({
83+
...get(pageStateAtom),
84+
modal: 'state',
85+
}))
86+
return { modalStateAtom }
87+
})
88+
89+
function Page() {
90+
const [modalOpen, setModalOpen] = useState(false)
91+
return (
92+
<>
93+
<Child />
94+
{modalOpen && <Modal />}
95+
</>
96+
)
97+
}
98+
99+
function Child() {
100+
const { childStateAtom } = useBunja(childBunja)
101+
const childState = useAtomValue(childStateAtom)
102+
// ...
103+
}
104+
105+
function Modal() {
106+
const { modalStateAtom } = useBunja(modalBunja)
107+
const modalState = useAtomValue(modalStateAtom)
108+
// ...
109+
}
110+
```
111+
112+
Notice that `pageBunja` is not directly `useBunja`-ed.
113+
114+
When you `useBunja` either `childBunja` or `modalBunja`, since they depend on `pageBunja`, it has the same effect as if `pageBunja` were also `useBunja`-ed.
115+
116+
When the modal is unmounted, there are no longer any places using `useBunja(modalBunja)`, so the instance of `modalBunja` is automatically destroyed.
117+
118+
### Dependency injection using Scope
119+
120+
You can use a bunja for local state management.
121+
122+
When you specify a scope as a dependency of the bunja, separate bunja instances are created based on the values injected into the scope.
123+
124+
```ts
125+
import { bunja, createScope } from 'bunja'
126+
127+
const UrlScope = createScope()
128+
129+
const fetchBunja = bunja([UrlScope], (url) => {
130+
const queryAtom = atomWithQuery((get) => ({
131+
queryKey: [url],
132+
queryFn: async () => (await fetch(url)).json(),
133+
}))
134+
return { queryAtom }
135+
})
136+
```
137+
138+
#### Injecting dependencies via React context
139+
140+
If you bind a scope to a React context, bunjas that depend on the scope can retrieve values from the corresponding React context.
141+
142+
In the example below, there are two React instances (`<ChildComponent />`) that reference the same `fetchBunja`, but since each looks at a different context value, two separate bunja instances are also created.
143+
144+
```tsx
145+
import { createContext } from 'react'
146+
import { bunja, createScope } from 'bunja'
147+
import { bindScope } from 'bunja/react'
148+
149+
const UrlContext = createContext('https://example.com/')
150+
const UrlScope = createScope()
151+
bindScope(UrlScope, UrlContext)
152+
153+
const fetchBunja = bunja([UrlScope], (url) => {
154+
const queryAtom = atomWithQuery((get) => ({
155+
queryKey: [url],
156+
queryFn: async () => (await fetch(url)).json(),
157+
}))
158+
return { queryAtom }
159+
})
160+
161+
function ParentComponent() {
162+
return (
163+
<>
164+
<UrlContext value="https://example.com/foo">
165+
<ChildComponent />
166+
</UrlContext>
167+
<UrlContext value="https://example.com/bar">
168+
<ChildComponent />
169+
</UrlContext>
170+
</>
171+
)
172+
}
173+
174+
function ChildComponent() {
175+
const { queryAtom } = useBunja(fetchBunja)
176+
const { data, isPending, isError } = useAtomValue(queryAtom)
177+
// Your component logic here
178+
}
179+
```
180+
181+
You can use the `createScopeFromContext` function to handle both the creation of the scope and the binding to the context in one step.
182+
183+
```ts
184+
import { createContext } from 'react'
185+
import { createScopeFromContext } from 'bunja/react'
186+
187+
const UrlContext = createContext('https://example.com/')
188+
const UrlScope = createScopeFromContext(UrlContext)
189+
```
190+
191+
#### Injecting dependencies directly into the scope
192+
193+
You might want to use a bunja directly within a React component where the values to be injected into the scope are created.
194+
195+
In such cases, you can use the inject function to inject values into the scope without wrapping the context separately.
196+
197+
```tsx
198+
import { inject } from 'bunja/react'
199+
200+
function MyComponent() {
201+
const { queryAtom } = useBunja(
202+
fetchBunja,
203+
inject([[UrlScope, 'https://example.com/']]),
204+
)
205+
const { data, isPending, isError } = useAtomValue(queryAtom)
206+
// Your component logic here
207+
}
208+
```

0 commit comments

Comments
 (0)