Security News
PyPI’s New Archival Feature Closes a Major Security Gap
PyPI now allows maintainers to archive projects, improving security and helping users make informed decisions about their dependencies.
value-enhancer
Advanced tools
A tiny library to enhance value with reactive wrapper.
Docs: https://value-enhancer.js.org
npm add value-enhancer
Object.defineProperty
nor Proxy
. Keeping everything as plain JavaScript value makes it easier to work with other libraries and easier for the JavaScript engine to optimize.value-enhancer
which makes it smaller and faster.MobX is cleverly designed to make properties magically reactive. But after using it in many of our large projects people started to complain about this implicit behavior. It is hard to tell if a property is reactive unless enforcing some kind of code style rules. Rules of MobX are easy to be broken especially for new team members.
MobX does not work well with other libraries. It could break other libraries if you forget to exclude instances from other libraries from making observable. toJS
is also needed if data is passed to other libraries.
MobX also prints error when it sees another version of MobX in the global. It is not a good choice for making SDK or library that will be delivered into customer's environment.
In my opinion, the vision of MobX has to be implemented as language-level features, otherwise it will create all kinds of compatibility issues. Svelte, SolidJS and even Vue are heading towards the compiler direction now, looking forward to the next generation of MobX.
In value-enhancer
reactive Vals and plain JavaScript values are easy to distinguish since they have different types. The values of reactive Vals are still plain JavaScript values so it works fine with other libraries. It is small and does not have global variable issues.
Vue3 brings Reactivity as standalone APIs. It is beautifully designed and I had learned a lot from its source code.
But even though it is made standalone, it is still very Vue centered. Many extra works related to Vue Components are added under the hood.
Vue supports lazy deep reactive conversion. It converts plain JavaScript values into reactive values which means it also suffers from the same issues of MobX.
It is a good choice if you are choosing the Vue ecosystem. The implementation of value-enhancer
absorbs many optimization strategies from Vue Reactivity while staying framework agnostic.
I love RxJS and the reactive paradigm behind it. But the goal of RxJS is to compose asynchronous or callback-based code. It is not optimized for state management.
It also requires you to write code in a pipe-able way which may not be acceptable for everyone.
The signature of combine
and derive
in value-enhancer
may look familiar to those who have used React hooks.
import { useMemo } from "react";
const derived = useMemo(() => source + 1, [source]);
I really like the explicit dependency declaration, but in React it is error-prone since people keep forgetting adding or removing dependencies. The React team even made a exhaustive-deps
linter rule for this.
value-enhancer
solves this by absorbing the RxJS-style callbacks.
import { val, derive, combine } from "value-enhancer";
const source$ = val(1);
console.log(source$.value); // 1
const derived$ = derive(source$, source => source + 1);
console.log(derived$.value); // 2
const combined$ = combine(
[source$, derived$],
([source, derived]) => source + derived
);
console.log(combined$.value); // 3
Since the type of reactive objects are different from its values, it is hard to have mismatched dependencies inside the transform
function.
value-enhancer
can be used in React with a super-simple hook use-value-enhancer
.
Svelte offers excellent support for Observables. Svelte store is one of the simplest implementations. The code is really neat and clean.
Svelte store works well for simple cases but it also leaves some edge cases unresolved. For example, when derived
a list of stores, the transform function could be invoked with intermediate states.
Svelte also adds a $xxx
syntax for subscribing Observables as values. The compiled code is really simple and straightforward.
value-enhancer
is compatible with Svelte Store contract. It can be used in Svelte just like Svelte stores.
SolidJS "create"s are like React hooks but with saner signatures. It is also thoughtfully optimized for edge cases.
A thing that one may feel odd in SolidJS is accessing reactive value by calling it as function. value-enhancer
keeps the xxx.value
way to access reactive value which I think should be more intuitive.
It also suffers from implicit magic issues like MobX and Vue where you ended up using something like mergeProps
and splitProps
.
value-enhancer
is compatible with SolidJS using from
.
Preact recently released Signals which shares similar ideas with value-enhancer
. It is like signals of SolidJS but without the odd function-like value accessing. It flushes reactions top-down then bottom-up like Vue and value-enhancer
.
The Preact team also took a step further to support writing Signals directly within TSX. This offers Svelte-like neat coding experience.
const count = signal(0);
// Instead of this:
<p>Value: {count.value}</p>
// … we can pass the signal directly into JSX:
<p>Value: {count}</p>
// … or even passing them as DOM properties:
<input value={count} />
But it also uses Vue-like magic to collect effects.
const counter = signal(0);
effect(() => {
console.log(counter.value);
});
It might seem clean at first but it's not a self-consistent solution either. You'll probably meet weird issues and find workarounds like signal.peek()
which is error-prone.
const counter = signal(0);
const effectCount = signal(0);
effect(() => {
console.log(counter.value);
// Whenever this effect is triggered, increase `effectCount`.
// But we don't want this signal to react to `effectCount`
effectCount.value = effectCount.peek() + 1;
});
This issue does not exist in value-enhancer
because we do not collect dependencies implicitly.
import { val } from "value-enhancer";
const count$ = val(2);
console.log(count$.value); // 2
count$.set(3);
console.log(count$.value); // 3
count$.value = 4;
console.log(count$.value); // 4
import { val, combine, derive } from "value-enhancer";
const count$ = val(3);
// Emit the current value synchronously, then emit the new value when it changes.
const disposeSubscribe = count$.subscribe(count => {
console.log(`subscribe: ${count}`);
}); // printed "subscribe: 3"
// Only emit the new value when it changes.
const disposeReaction = count$.reaction(count => {
console.log(`reaction: ${count}`);
}); // (nothing printed)
// `===` equality check by default
count$.set(3); // nothing happened
// subscription triggered asynchronously by default
count$.set(4); // nothing happened
await Promise.resolve(); // wait for the next tick
// printed "subscribe: 4"
// printed "reaction: 4"
disposeSubscribe();
disposeReaction();
Derive a new Val from another Val.
import { val, derive } from "value-enhancer";
const count$ = val(2);
const derived$ = derive(count$, count => count * 3);
console.log(derived$.value); // 6
Combine multiple Vals into a new Val.
import { val, derive, combine } from "value-enhancer";
const count$ = val(2);
const derived$ = derive(count$, count => count * 3);
const combined$ = combine(
[count$, derived$],
([count, derived]) => count + derived
);
console.log(combined$.value); // 8
Unwrap the inner Val from a Val of Val. This is useful for subscribing to a dynamic Val that is inside another Val.
import { val, unwrap } from "value-enhancer";
const itemList$ = val([val(1), val(2), val(3)]);
const firstItem$ = unwrap(itemList$, itemList => itemList[0]);
console.log(firstItem$.value); // 1
itemList$.set([val(4), val(5), val(6)]);
console.log(firstItem$.value); // 4
By default, ===
equality check is used to determine whether a value has changed. You can customize the equality check by passing a compare
function.
import { val } from "value-enhancer";
const isSameXYPosition = (p1, p2) => p1.x === p2.x && p1.y === p2.y;
const isSameXYZPosition = (p1, p2) =>
p1.x === p2.x && p1.y === p2.y && p1.z === p2.z;
const xyzPosition$ = val({ x: 0, y: 0, z: 0 }, { compare: isSameXYZPosition });
const xyPosition$ = derive(xyPosition, { compare: isSameXYPosition });
xyPosition$.set({ x: 0, y: 0, z: 0 }); // nothing happened
Subscription is triggered asynchronously on next tick by default. To trigger synchronously, set eager
parameter to true
.
count$.subscribe(count => console.log(`subscribe: ${count}`), true);
count$.reaction(count => console.log(`reaction: ${count}`), true);
Or set eager
to true
when creating the Val.
// subscription of count$ is trigger synchronously by default
const count$ = val(3, { eager: true });
const derived$ = derive(count$, count => count * 3, { eager: true });
You can create your own Val by extending ReadonlyValImpl
class, or implement the ReadonlyVal
interface and mark manually with markVal
function.
import { ReadonlyValImpl, isVal } from "value-enhancer";
class MyVal extends ReadonlyValImpl {
constructor(value) {
super(value);
}
set(value) {
console.log("set", value);
this._set(value);
}
}
const myVal = new MyVal(11);
myVal.set(22); // printed "set 22"
console.log(isVal(myVal)); // true
import { val, isVal, markVal, identity } from "value-enhancer";
const createMyVal = value => {
const v = val(value);
const myVal = {
value: 11,
compare: identity,
reaction: v.reaction.bind(v),
subscribe: v.subscribe.bind(v),
unsubscribe: v.unsubscribe.bind(v),
set(value) {
console.log("set", value);
this.value = value;
},
};
markVal(myVal);
return myVal;
};
const myVal = createMyVal(11);
myVal.set(22); // printed "set 22"
console.log(isVal(myVal)); // true
FAQs
Enhance value with plain and explicit reactive wrapper. Think of it as hook-style signals.
The npm package value-enhancer receives a total of 1,065 weekly downloads. As such, value-enhancer popularity was classified as popular.
We found that value-enhancer demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
PyPI now allows maintainers to archive projects, improving security and helping users make informed decisions about their dependencies.
Research
Security News
Malicious npm package postcss-optimizer delivers BeaverTail malware, targeting developer systems; similarities to past campaigns suggest a North Korean connection.
Security News
CISA's KEV data is now on GitHub, offering easier access, API integration, commit history tracking, and automated updates for security teams and researchers.