Onek
⚡️ 1.8KB full-featured state management inspired by MobX and Solid, batteries included ⚡️
Install / Use
/learn @zheksoon/OnekREADME
Onek (reads as one-kay or on-ek) is a simple but powerful state management library for React based on a solid foundation of functional reactive data structures from MobX and Solid.js. It provides everything needed for managing state in complex React applications, all in a less than 2KB package.
Features
- 🚀 Reactive Observable and Computed Values - Inspired by MobX, Solid.js and Preact Signals
- 🎭 Both MobX and Solid.js Flavors - Feel free to choose and mix the styles that best fit your needs
- 🤔 Not Opinionated - Use global state, relational or object-oriented models - whatever you need to do your task
- 👁 Transparency - Everything is cached and up-to=date, no worries!
- 💧 No Memory Leaks - No subscription to observable - no leaks, it is
- 🧩 Single Hook - Just one to make your components reactive
- 🔀 Concurrent React Features - out-of-the-box support to blow the performance
- 🤓 Built-in Shallow Equality - optimizations, optimizations everywhere...
- 💾 Compatibility - Has ES6? No worries then!
- 💯 100% Test Coverage - Nothing is missed
- ⭐️ Fully TypeScript - No comments, as is
- ☯️ Beauty Inside - "Nothing to add, nothing to take away"
- 📦 ...and all in a less than 2KB package
Table of contents
Installation
yarn add onek
npm install --save onek
Show me the code
Here's an example of a counter app that showcases all the main features of Onek with React:
import { action, computed, observable } from "onek";
import { useObserver } from "onek/react";
// defined observable value
const [count, setCount] = observable(0);
// define computed values derived from the observable
const canIncrease = computed(() => count() < 10);
const canDecrease = computed(() => count() > 0);
// defined actions that manipulate observable values
const increase = action(() => {
if (canIncrease()) {
setCount((count) => count + 1);
}
});
const decrease = action(() => {
if (canDecrease()) {
setCount((count) => count - 1);
}
});
const Counter = () => {
// get observer instance
const observer = useObserver();
// wrap your render code with the observer to make it reactive
return observer(() => (
<div>
<p>Count: {count()}</p>
<button disabled={!canDecrease()} onClick={decrease}>
-
</button>
<button disabled={!canIncrease()} onClick={increase}>
+
</button>
</div>
));
};
// two counters rendered in sync
root.render(
<>
<Counter />
<Counter />
</>
);
Introduction
Note: in this section Solid.js flavor will be used. If you want examples of MobX flavor, check out the MobX flavor section.
Observable values
If you're familiar with React's useState hook, you're already halfway to understanding Onek's observable function. Like the useState hook, it accepts an initial value and returns a tuple of value getter and setter. The difference is that the value getter is a function that returns the value instead of the value itself:
import { observable } from "onek";
// create observable value
const [greeting, setGreeting] = observable("hello!");
// set value directly
setGreeting("hola!");
// set value with updater function
setGreeting((oldGreeting) => oldGreeting + "!!!");
greeting() === "hola!!!!";
Please note that while it's similar to React's useState, it shouldn't be used in a React component. In this case, use the useObservable hook described in Using with React section.
observable supports an equality check function as a second argument. This function can be used to prevent unnecessary updates when the value hasn't effectively changed. You can also use true to use the built-in shallowEquals implementation:
import { shallowEquals } from "onek";
const [greetings, setGreetings] = observable(["hello"], true);
// or equivalently
const [greetings, setGreetings] = observable(["hello"], shallowEquals);
// setting an equal value doesn't trigger updates
setNumber(["hello"]);
Built-in shallowEquals covers plain objects, arrays, Map and Set equality, but if you need something else (like lodash isEqual), just pass it as the second argument.
In Onek, you can store functions directly in an observable. This is useful for cases where you need to store callback or computation functions. To do this, pass true as the second argument to the setter function:
// create an observable for a callback function
const [callback, setCallback] = observable(() => console.log("hello!"));
// stores the callback as is
setCallback(() => console.log("hola!"), true);
</details>
Computed values
A computed value is like useMemo in React - it's cached and returns the cached value afterward.
All accessed observable or other computed values are automatically tracked, there is no need to
specify a dependency list.
Changes to these tracked values automatically invalidate the cached value, which is recalculated on the next access to the computed:
import { computed } from "onek";
const loudGreeting = computed(() => greeting().toUpperCase());
loudGreeting() === "HOLA!!!!";
setGreeting("hi!");
loudGreeting() === "HI!";
<details>
<summary><b>Extra:</b> equality check argument</summary>
Just like with observable, you can also provide an equality check function as a second argument to computed (or true for default shallowEquals implementation). This allows you to control when the computed value is considered to have changed and needs to notify its subscribers about it. In case the equality check function returns true, the output of the computed remains referentially equal to the old one:
// create observable with an array of numbers
const [numbers, setNumbers] = observable([1, 2, 3, 4]);
// create a computed value that returns sorted array
const sortedNumbers = computed(() => [...numbers()].sort(), true);
const result = sortedNumbers();
console.log(result); // output: [1,2,3,4]
// the array is different, but sorted result is the same
setNumbers([4, 3, 2, 1]);
sortedNumbers() === result; // result is referrentially the same
The primary goal of the equality check argument is to manage and limit side effects, such as updates to React components or executions of reaction functions. These side effects might occur due to changes in the source observable or computed values. By using an equality check, you can ensure that these side effects are triggered only when the result of the computed function changes substantially, rather than being activated by every minor change to the source values. This approach can be particularly useful when the source values change frequently, but the computed result does not.
Using with React
Using observable and computed in React components is as simple as:
import { computed, observable, useObserver } from "onek";
const [greeting, setGreeting] = observable("hello!");
const loudGreeting = computed(() => greeting().toUpperCase());
const LoudGreeting = () => {
const observer = useObserver();
return observer(() => <p>{loudGreeting()}</p>);
};
const GreetingInput = () => {
const observer = useObserver();
return observer(() => (
<input
type="text"
onChange={(e) => setGreeting(e.target.value)}
value={greeting()}
/>
));
};
root.render(
<>
<GreetingInput />
<LoudGreeting />
</>
);
useObserver hook has no arguments and returns an observer function. You can wrap your component
code with it or pass it to observable and computed getters to get the component update
on their changes. Reading observable values outside of the observer fn or without passing it to
getters won't subscribe the component to changes:
const [value, setValue]
Related Skills
bluebubbles
344.4kUse when you need to send or manage iMessages via BlueBubbles (recommended iMessage integration). Calls go through the generic message tool with channel="bluebubbles".
node-connect
344.4kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
slack
344.4kUse when you need to control Slack from OpenClaw via the slack tool, including reacting to messages or pinning/unpinning items in Slack channels or DMs.
frontend-design
99.2kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
