Options
All
  • Public
  • Public/Protected
  • All
Menu

valup - v0.2.1

Valup

version npm gitlab license codecov

A small, stupid-simple, reactive library.

Why?

Because I wanted to reinvent the wheel.

Actually, because I discovered Solid.js and I find it interesting, especially with its signals. Then, I browsed the source code and found a reference to S.js. I read it and loved it. But the source code of S.js is quite hard for me to understand. So I made my own reactive library.

Is it any good?

I don't know. It works for me I guess so it's not all bad, but I recommend you use a more known project like S.js or a whole solution like Redux. This mainly serves as an educational resource and an example of how such a thing could be implemented in few lines of code. The only case in which I'd recommend using this library is if you're bothered by import size (on browser, minify the project before importing it) or by performance - although I never did any benchmark so I suppose S.js and others could be faster than this project.

Usage

You can create a reactive value with the factory methods of the R class (we'll cover the differences between them later), then update the internal value of the object with the val get/set accessors.

import { R } from './src/index.ts';
const myData = R.data("Hello");
const myStrictValue = R.value("World");
myData.val = myStrictValue.val;
// Events fired...

To add an event handler, you can use either on or addHandler, and remove them with removeHandler.

const counter = R.data(0);
const handler = ({state}: {state: any}) => {
console.log(`Changed from ${state.prev} to ${state.next}.`);
}

counter.on("changed", handler);
counter.val = counter.val + 1; // "Changed from 0 to 1."
counter.val = counter.val + 1; // "Changed from 1 to 2."

counter.removeHandler("changed", handler);
counter.val = counter.val + 1; // Doesn't log anything.
counter.val = counter.val + 1; // Doesn't log anything.

As you can see in the example above, the "changed" event is already defined internally and is fired after the new value has been set, the state given is of the form {prev: ..., current: ..., next: ..., target: [object R]}. Another predefined event is "changing" which is called right before the new value has been assigned and with the same state structure.

You can also use on to fire custom events instead of just these two, but you have to emit them manually with notify.

const hello = ({state}: {state: any}) => {
console.log(`Hello, ${state.current}!`);
}
const goodbye = ({state}: {state: any}) => {
console.log(`Goodbye, ${state.current}.`);
}

const previousUsers = [];
const username = R.data("Alice")
.on("changing", () => {username.notify("keep-history", {name: username.val})})
.on("changing", goodbye)
.on("changed", hello)
.on("keep-history", ({state}: {state: any}) => { previousUsers.push(state.name) });

username.val = "Bob";
// (previousUsers is now ["Alice"])
// "Goodbye, Alice."
// "Hello, Bob!"
username.val = "Charly";
// (previousUsers is now ["Alice","Bob"])
// "Goodbye, Bob."
// "Hello, Charly!"
username.notify("keep-history", {name: username.val});
// (previousUsers is now ["Alice","Bob","Charly"])

You may want to execute functions only when the value is different and not when it is reassigned to the same thing, in this case, you should use R.value instead of R.data.

const loggedIn = R.value(false)
.on("changed", console.log.bind(console, "User logged in or logged out."));
loggedIn.val = false;
loggedIn.val = false;
loggedIn.val = true; // "User logged in or logged out."
loggedIn.val = true;
loggedIn.val = true;
loggedIn.val = false; // "User logged in or logged out."

When used with an array or object, you may provide your own equality function to check if the events are to be sent or not.

const jsonEqual = (a: any, b: any) => JSON.stringify(a) === JSON.stringify(b);
const state = R.value({id: 0, logged: false, isAdmin: false}, jsonEqual)
.on("changed", ({state}: {state: any}) => {
const event = state.current.logged ? "logged-in" : "logged-out";
state.notify(event, state);
})
.on("logged-in", ({state}: {state: any}) => console.log(`User ${state.current.id} logged in.`))
.on("logged-out", ({state}: {state: any}) => console.log(`User ${state.current.id} logged out.`));

const toggleLog = (obj: any) => {
return { ...obj, logged: !obj.logged };
}
state.val = toggleLog(state.val); // "User 0 logged in."
state.val = toggleLog(state.val); // "User 0 logged out."

If your only need in a state is the current value, you can send the currentValue property.

const countdown = R.data(0)
.on("down", ({state}: {state: any}) => {
console.log(state.current);
if (state.current > 0) {
countdown.val = countdown.val - 1;
}
})
.on("changed", ({state}: {state: any}) => {countdown.notify("down", countdown.currentState)});

countdown.val = 5;
// "5"
// "4"
// "3"
// "2"
// "1"
// "0"

You may watch for multiple values with the static R.onAny method like so.

const c1 = R.data(1);
const c2 = R.data(2);
const c3 = R.data(3);
const handler = ({state}: {state: any}) => {console.log("Something is going on...")}
R.onAny([c1, c2, c3], "changing", handler); // Same as applying the handler to c1, c2 and c3.
R.onAnyChanging([c1, c2, c3], handler); // Shorthand for "changing" event.
R.onAnyChanged([c1, c2, c3], handler); // Shorthand for "changed" event.

License

This is free and unencumbered software released into the public domain. For more information, read the LICENSE file or refer to http://unlicense.org/.

Generated using TypeDoc