@mrkev/linked-state
v0.0.4
Published
React-compatible (incl. React Compiler) state-management library for React. **Prioritizes efficiency.**
Maintainers
Readme
LinkableState
React-compatible (incl. React Compiler) state-management library for React. Prioritizes efficiency.
There's 4 primitives:
- LinkableValue
- LinkableArray
- LinkableMap
- LinkableSet
There's 3 key features:
- These state objects are mutable, but will trigger react updates when "linked" to a component.
- These state objects can be nested inside one another. When linking, you can listen to shallow or deep updates.
- This is all React Compiler compatible.
LinkableValue
Wraps a value to make it linkable:
// creates a linkable value from this number
const countState = lValue(0);
count.get(); // reads the value
count.set(); // sets the valueUsage with React:
const counterState = lValue(0);
function App() {
// link this component to counterState. When counterState
// is updated, this component will re-render.
const counter = useLink(counterState);
return (
<div>
{/* counter() gets the latest counterState */}
count: {counter().get()}
{/* state is updated directly on linkable */}
<button onClick={() => counterState.set(counter().get() + 1)}>
increment
</button>
</div>
);
}
// the linkable can be updated outside react too
window.addEventListener("keydown", function () {
counter.set(counter.get() + 1);
});LinkableArray
Creates a linkable Array-like:
const datesState = lArray([new Date()]);
datesState.at(0);
datesState.push(new Date());
datesState.map((date) => {});
// etc...LinkableMap
Creates a linkable Map:
const taskState = lMap([
["buy milk", "done"],
["buy eggs", "pending"],
]);
taskState.set("buy eggs", "done");
taskState.get("buy milk");
taskState.has("buy tortillas"); // false
// etc...LinkableSet
Creates a linkable Set:
const visitorsState = lSet(["Ajay", "Kevin"]);
visitorsState.add("Ajay");
visitorsState.delete("Kevin");
visitorsState.size; // 2
// etc...Nested Structures
Linkables are nestable. This allows you to mutate your application state, but still trigger state updates where you need them
Example:
// represents a task list
const listState = lMap([
["buy milk", lValue(true)],
["buy eggs", lValue(false)],
]);
function ListExample() {
const list = useLink(listState);
return (
<>
<ListSummary />
<ul>
{list()
.entries()
.map(([name, doneState], i) => (
<ListItem key={i} name={name} doneState={doneState} />
))
.toArray()}
</ul>
</>
);
}
function ListSummary() {
// second argument: listen to recursive changes.
// ie, this component will re-render when the done
// state for list items changes
const todoList = useLink(listState, true);
const done = todoList()
.entries()
.filter(([listItem, done]) => {
return done.get() == true;
})
.toArray();
return <i>Tasks done: {done.length}</i>;
}
function ListItem({
name,
doneState,
}: {
name: string;
doneState: LinkableValue<boolean>;
}) {
const done = useLink(doneState);
return (
<li>
{name}:{" "}
{done().get() ? (
"done"
) : (
<button onClick={() => doneState.set(true)}>mark done</button>
)}
</li>
);
}In the example above, when buy eggs is marked as done:
- no new data structure is created, good for memory use
- state isn't iterated to be updated, good for performance
- only
List Summary, and the "buy eggs"ListItemwill be updated (not the "buy milk"ListItem), good for performance - no "set" functions have to be passed around, succint
