Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

State Lifetime Manager

NotificationsYou must be signed in to change notification settings

disjukr/bunja

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

86 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Bunja is lightweight State Lifetime Manager.
Heavily inspired byBunshi.

Definition: Bunja (分子 / 분자) - Korean for molecule, member or element.

Why is managing the lifetime of state necessary?

Global state managers like jotai or signals offer the advantage of declarativelydescribing state and effectively reducing render counts, but they lack suitablemethods for managing resources with a defined start and end.
For example, consider establishing and closing a WebSocket connection or a modalform UI that appears temporarily and then disappears.

Bunja is a library designed to address these weaknesses.
Each state defined with Bunja has a lifetime that begins when it is firstdepended on somewhere in the render tree and ends when all dependenciesdisappear.

Therefore, when writing a state to manage a WebSocket, you only need to create afunction that establishes the WebSocket connection and a disposal handler thatterminates the connection.
The library automatically tracks the actual usage period and calls the init anddispose as needed.

So, do I no longer need jotai or other state management libraries?

No. Bunja focuses solely on managing the lifetime of state, so jotai and otherstate management libraries are still valuable.
You can typically use jotai or something, and when lifetime management becomesnecessary, you can wrap those states with bunja.

How to use

Bunja basically provides two functions:bunja anduseBunja.
You can usebunja to define a state with a finite lifetime and use theuseBunja hook to access that state.

Defining a Bunja

You can define a bunja using thebunja function. When you access the definedbunja with theuseBunja hook, a bunja instance is created.
If all components in the render tree that refer to the bunja disappear, thebunja instance is automatically destroyed.

If you want to trigger effects when the lifetime of a bunja starts and ends, youcan use thebunja.effect function.

import{bunja}from"bunja";import{useBunja}from"bunja/react";constcountBunja=bunja(()=>{constcountAtom=atom(0);bunja.effect(()=>{console.log("mounted");return()=>console.log("unmounted");});return{ countAtom};});functionMyComponent(){const{ countAtom}=useBunja(countBunja);const[count,setCount]=useAtom(countAtom);// Your component logic here}

Defining a Bunja that relies on other Bunja

If you want to manage a state with a broad lifetime and another state with anarrower lifetime, you can create a (narrower) bunja that depends on a (broader)bunja. For example, you can think of a bunja that manages the WebSocketconnection and disconnection, and another bunja that subscribes to a specificresource over the connected WebSocket.

In an application composed of multiple pages, you might want to subscribe to theFoo resource on page A and the Bar resource on page B, while using the sameWebSocket connection regardless of which page you're on. In such a case, you canwrite the following code.

// To simplify the example, code for buffering and reconnection has been omitted.constwebsocketBunja=bunja(()=>{letsocket;constsend=(message)=>socket.send(JSON.stringify(message));constemitter=newEventEmitter();conston=(handler)=>{emitter.on("message",handler);return()=>emitter.off("message",handler);};bunja.effect(()=>{socket=newWebSocket("...");socket.onmessage=(e)=>emitter.emit("message",JSON.parse(e.data));return()=>socket.close();});return{ send, on};});constresourceFooBunja=bunja(()=>{const{ send, on}=bunja.use(websocketBunja);constresourceFooAtom=atom();bunja.effect(()=>{constoff=on((message)=>{if(message.type==="foo")store.set(resourceAtom,message.value);});send("subscribe-foo");return()=>{send("unsubscribe-foo");off();};});return{ resourceFooAtom};});constresourceBarBunja=bunja(()=>{const{ send, on}=bunja.use(websocketBunja);constresourceBarAtom=atom();// ...});functionPageA(){const{ resourceFooAtom}=useBunja(resourceFooBunja);constresourceFoo=useAtomValue(resourceFooAtom);// ...}functionPageB(){const{ resourceBarAtom}=useBunja(resourceBarBunja);constresourceBar=useAtomValue(resourceBarAtom);// ...}

Notice thatwebsocketBunja is not directlyuseBunja-ed. When youuseBunjaeitherresourceFooBunja orresourceBarBunja, since they depend onwebsocketBunja, it has the same effect as ifwebsocketBunja were alsouseBunja-ed.

Note

When a bunja starts, the initialization effect of the bunja with a broaderlifetime is called first.
Similarly, when a bunja ends, the cleanup effect of the bunja with the broaderlifetime is called first.
This behavior is aligned with how React'suseEffect cleanup function isinvoked, where the parent’s cleanup is executed before the child’s in therender tree.

See:facebook/react#16728

Dependency injection using Scope

You can use a bunja for local state management.
When you specify a scope as a dependency of the bunja, separate bunja instancesare created based on the values injected into the scope.

import{bunja,createScope}from"bunja";constUrlScope=createScope();constfetchBunja=bunja(()=>{consturl=bunja.use(UrlScope);constqueryAtom=atomWithQuery((get)=>({queryKey:[url],queryFn:async()=>(awaitfetch(url)).json(),}));return{ queryAtom};});

Injecting dependencies via React context

If you bind a scope to a React context, bunjas that depend on the scope canretrieve values from the corresponding React context.

In the example below, there are two React instances (<ChildComponent />) thatreference the samefetchBunja, but since each looks at a different contextvalue, two separate bunja instances are also created.

import{createContext}from"react";import{bunja,createScope}from"bunja";import{bindScope}from"bunja/react";constUrlContext=createContext("https://example.com/");constUrlScope=createScope();bindScope(UrlScope,UrlContext);constfetchBunja=bunja(()=>{consturl=bunja.use(UrlScope);constqueryAtom=atomWithQuery((get)=>({queryKey:[url],queryFn:async()=>(awaitfetch(url)).json(),}));return{ queryAtom};});functionParentComponent(){return(<><UrlContextvalue="https://example.com/foo"><ChildComponent/></UrlContext><UrlContextvalue="https://example.com/bar"><ChildComponent/></UrlContext></>);}functionChildComponent(){const{ queryAtom}=useBunja(fetchBunja);const{ data, isPending, isError}=useAtomValue(queryAtom);// Your component logic here}

You can use thecreateScopeFromContext function to handle both the creation ofthe scope and the binding to the context in one step.

import{createContext}from"react";import{createScopeFromContext}from"bunja/react";constUrlContext=createContext("https://example.com/");constUrlScope=createScopeFromContext(UrlContext);

Injecting dependencies directly into the scope

You might want to use a bunja directly within a React component where the valuesto be injected into the scope are created.

In such cases, you can use the second parameter ofuseBunja hook to injectvalues into the scope without wrapping the context separately.

functionMyComponent(){const{ queryAtom}=useBunja(fetchBunja,[UrlScope.bind("https://example.com/")],);const{ data, isPending, isError}=useAtomValue(queryAtom);// Your component logic here}
Doing the same thing inside a bunja

You can usebunja.fork to inject scope values from within a bunjainitialization function.

constmyBunja=bunja(()=>{constfooData=bunja.fork(fetchBunja,[UrlScope.bind("https://example.com/foo"),]);constbarData=bunja.fork(fetchBunja,[UrlScope.bind("https://example.com/bar"),]);return{ fooData, barData};});

Contributors4

  •  
  •  
  •  
  •  

[8]ページ先頭

©2009-2025 Movatter.jp