Predictable micro state manager.
This package has no dependencies and weights less than 1kB (and less than 500B gzipped).
Manatea
uses weird and funny names because this project is mostly for fun. And also we can do whatever we want with it.
Here is the reason behind all our names:
First you
order a cup
oftea
filled with yourfirst tea
. Then you can place a neworder
and thewaiter
willrefill
yourcup
with a new tea.
Depending on whether or not the tea suits you, you can add flavor to it with aflavoring
.
But as those can be confusing, he is an equivalent to all our names in more usual words:
In manatea |
More generic names | Explanation |
---|---|---|
orderCup |
create atom (store) | Create an object that can contain a value, gives access to read or modify this value, and provides a way to add listeners that will get called when the value changes. |
flavoredTea |
value | Value stored within the cup |
firstTea |
initial value | Value present within the cup when it was first created |
Order |
value setter | When the cup needs to be updated, we can either provide a new value, or a function that will give access to the current value in order to compute the new one. |
unflavoredTea |
new value | New value provided to the cup that will be used as the new internal value (after going through the flavoring phase). Either the value of the order, or its returned value if it was a function. |
flavoring |
transformer | When a unflavoredTea (dirty value) is passed to the cup, you may want to restrict it to a range of allowed values. The flavoring can transform this provided unflavoredTea to a flavoredTea one (a clean value). |
waiter |
listener | When the internal tea changes, waiters will get called with the new value. |
import { orderCup } from 'manatea';
// Define (order) a cup with a default value (tea) of 0
const cup = orderCup(0);
// Return current value (tea) of the cup
cup(); // 0;
// Set value (tea) to 1
cup(1);
// increment the stored value (tea) by 1
cup(tea => tea + 1);
// Supports async functions
// wait 1 second and then increment the tea by 5
cup(async tea => {
await sleep(1);
return tea + 5;
});
// You can also read from cup to set another cup's value
cup(tea => {
const otherTea = otherCup();
return tea + otherTea;
});
Every update made to a cup returns a promise:
// wait for the value to be stored and dispatched
await cup(1);
// you can also use the regular .then() method
cup(tea => tea + 1).then(tea => console.log(tea));
When a new unflavored tea is passed to the cup, you may want to apply restrictions to it.
For instance, if your flavoredTea
is a number, you may want to set bounds to it:
const cup = createCup(0, unflavoredTea =>
Math.min(10, Math.max(0, unflavoredTea)),
);
cup(12);
// clamp to 10 as we set an upper bound to 10 above
cup(); // 10
In addition simple restrictions that have the same type as the flavored tea, you can fully transform it:
interface FlavoredTea {
value: number;
label: string;
}
const cup = createCup<FlavoredTea, number>(0, unflavored => {
return {
value: unflavored,
label: `Value of: ${unflavored}`,
};
});
// even the initialTea will get flavored
cup(); // { value: 0, label: 'Value of: 0' }
cup(5);
// even the initialTea will get flavored
cup(); // { value: 5, label: 'Value of: 5' }
The flavoring
function have a second parameters (not passed for the initialTea
) that give to you the previously flavoredTea
:
const cup = createCup(
0,
(unflavored, previouslyFlavoredTea = 0) => unflavored + previouslyFlavoredTea,
);
cup(); // 0
cup(2);
cup(); // 2 – because 2 + 0 = 2
cup(5);
cup(); // 7 – because 5 + 2 = 7
From any cup, you can call waiters that will alert you when the tea stored in the cup changes (they are like event listeners).
// Call the waiter
const waiter = cup.on(tea => console.log(tea));
You can fire a waiter (stop the listener) by calling it:
const waiter = cup.on(console.log);
// here the waiter will be called on each update of the cup
// but by calling `waiter()`, it won't receive any update anymore
waiter();
You can create a cup based on another one, or multiple other ones:
const cup1 = orderCup(0);
const cup2 = orderCup(0);
const derivedCup = orderCup(sip => sip(cup1) + sip(cup2));
derivedCup(); // 0
await cup1(1);
await cup1(4);
derivedCup(); // 5
And it supports the same feature are regular cup:
- flavoring
- waiters
- it's tea can be updated
const countString = orderCup('0');
const cumulatedCountNumber = orderCup(
sip => sip(countString),
(newValue, previousTotal = 0) => {
return Number(newValue) + previousTotal;
},
);
cumulatedCountNumber.on(newTotal => console.log(newTotal));
await countString('5');
cumulatedCountNumber(); // 5 (0 + 5)
// you can also call `cumulatedCountNumber` directly if you want:
await cumulatedCountNumber('12');
cumulatedCountNumber(); // 17 (5 + 12)