bonezjs v0.1.3
What is Bonez?
Bonez is a state library for React that takes the state out from React for easier state management.
Each unit of state is called bone, and all the bones build the entire state skeleton of the application.
A unit of state can be as simple as a boolean, number or a string, but it can also be a complex data structure such as an array, object or a function.
How Bonez differs from useState?
While useState is a React hook, a bone is a simple JS object with some methods and internal values.
A bone exposes 4 methods:
getValue- this method returns the currentbonevalue.setValue- this method sets a new value of thebone. If the value is the same as the current value (same value forboolean,number,string, same reference forarray,objectorfunction), by default, nothing changes. This can be controlled by passingcomparefunction to decide when a value is the same as the previous value.watch- this adds a watcher for thebonevalue, every time thebonevalue is changed via thesetValuemethod, all watchers are triggered with the new value. For easy unwatch, every call towatchreturns anunwatchvalue to remove the specific watcher that was just added. To keep things performant and clean, watchers callbacks are indexed by their function reference, so adding the same function again as a watcher will actually not add another watcher.unwatch- this removes the provided watcher callback from the watchers list. Does nothing if the provided watcher function does not exist on the list.
So how it works with React?
By the use of hooks.
When you pass a bone to useBone / useBoneValue / useDerivedBoneValue - you are making the bone and your React component "connected". Every change to that bone will trigger a render for all the components that have one of those hooks.
The useBoneSet hook WILL NOT trigger a re-render of the component alone, unless the component also has one of the other useBone* hooks. This is good for performance, since if your component only need to trigger a change to a value, but it is not dependent on it, it will not rerender.
Note: Under the hood, all useBone* hooks (except for useBoneSet) are based on useState to trigger component renders.
Installation
Add bonezjs using your favorite package manager. Foe example, using npm:
npm install bonezjsExamples
Simple example
import { bone, useBoneValue, useBoneSet } from 'bonez'
const counter = bone(0)
const CounterDigits = () => {
const count = useBoneValue(counter)
return (
<div>
<h1>{count}</h1>
</div>
)
}
const CounterControls = () => {
const setCount = useBoneSet(counter)
return (
<div>
<button onClick={() => setCount((count) => count + 1)}>Count</button>
</div>
)
}
const Counter = () => {
return (
<>
// This will render everytime counter bone value changes
<CounterDigits />
// This will render only once, since it does not depends on counter bone value
<CounterControls />
</>
)
}Pass a custom compare function
import { bone, useBoneValue, useBoneSet } from 'bonez'
interface User {
email: string,
firstName: string,
lastName: string
}
const userA : User = {
email: 'a@test.com',
firstName: 'A',
lastName: 'A'
}
const userB : User = {
email: 'b@test.com',
firstName: 'B',
lastName: 'B'
}
const user = bone<User|null>(null, {
compare: (value, newValue) => value?.email === value?.email
})
const UserProfile = () => {
const userProfile = useBoneValue(user)
if (!userProfile) return null
return (
<div>
<h1>{userProfile.email}</h1>
<h2>{userProfile.firstName} {userProfile.lastName}</h2>
</div>
)
}
const Login = () => {
const setUser = useBoneSet(user)
return (
<div>
<button onClick={() => setUser({ ...userA })}>Login user A</button>
<button onClick={() => setUser({ ...userB })}>Login user B</button>
</div>
)
}
const UserApp = () => {
return (
<>
// This will render everytime user bone value changes, but since we compare by email,
// if we set the same user again, even though we create a new object reference every time,
// it will not be rendered
<UserProfile />
// This will render only once, since it does not depends on user bone value
<Login />
</>
)
}This is useful to prevent redundant renders if nothing actually changes, a simple example will be to compare objects that are returning from the server, and have id and updatedAt fields for example - if the same id was set, and the updatedAt value is not different, than the object was not actually changed and there is no need to render the component again.