atomi
v0.2.4
Published
JS Reactivity Framework
Downloads
337
Readme
atomi
A reactivity framework.
Installation
npm i atomi
Example
import { atom, reactive } from "atomi"
const [counter, setCounter, setCounterFn] = atom(0)
reactive(() => {
console.log(`counter is: ${counter()}`)
})
await setCounter(1) // Logs `counter is: 1`
await setCounterFn(count => count + 1) // Logs `counter is: 2`
References
Hooks
This is the part that majority of users are going to interact with.
reactive
- src/hooks.mjs#6
- Accept
(scope) => undefined
- Return
Promise<Scope>
orScope
import { reactive } from "atomi"
Used to create reactive functions. Reactive functions are such functions that execute automatically when atoms inside them change their value
| Argument | Type | Desc |
|---|:---|:---|
| callback | [async] Function
| Called when atoms inside change Accepts a single argument scope |
| ignoreAsync | Boolean
| By default is false
. If true
is passed return will be always scope even if callback resovles into Promise
|
Returns scope or Promise
that resolves into scope when callback
is async
or returns Promise
reactive(scope => {
const value = dependantAtom()
scope.stop()
})
atom
- src/hooks.mjs#82
- Accept
Any
- Return
[() => Any, (value) => Promise, (Function) => Promise]
import { atom } from "atomi"
Atoms are used to store information. When they are accessed from inside reactive functions, atom registers function's scope as dependency. Any time atom is updated, it's functions of dependant scopes are executed.
| Argument | Type | Desc |
|---|:---|:---|
| Default Value | Any
| Intial value of atom |
Returns Array of getter, setter and callback setter functions
getter function
that returns current value of the atom and registers current scope as dependecy if current scope existssetter function
accepts a value that will become new value of the atom. Returns aPromise
that fulfills when all dependant scopes of the atom are executedcallback setter
accepts a callback function that accepts 2 arguments and that returns new value for the atom orNONE
. Returns aPromise
that fulfills when all dependant scopes of the atom are executed
callback setter
| Argument | Type | Desc |
|---|:---|:---|
| Current Value | Any
| The current value of atom |
| NONE
| Symbol
| Unique symbol. If NONE
is returned the atom retains it's value and reactive updates will not follow |
Returns Any
or NONE
const [count, setCount, setCountFn] = atom(0)
reactive(function logCount() {
// getter function
console.log(count())
})
// setter function
await setCount(2)
// callback setter function
await setCountFn((current, NONE) => current + 1) // Sets count to 3 and triggers logCount to execute
await setCountFn((current, NONE) => NONE) // Count stays at 3 and logCount is not triggered.
// NONE is useful when the complex logic produces the same value as current
// and hence we do not want to update anything because it says the same.
new atom
- src/hooks.mjs#82
- Accept
Any
- Return RectiveVar
import { atom } from "atomi"
| Argument | Type | Desc |
|---|:---|:---|
| Default Value | Any
| Intial value of atom |
Returns RectiveVar with default value
const count = new atom(0)
reactive(function logCount() {
console.log(count.get())
})
await count.set(1) // sets atom count value to 1 and triggers logCount
nonreactive
- src/hooks.mjs#46
- Accept
Function
- Return
Any
import { nonreactive } from "atomi"
Used as wrapper around an atom to access its value, but not register current scope as its dependency
| Argument | Type | Desc |
|---|:---|:---|
| Callback | Function
| Getter function or atomic function |
Returns the return value of the callback function
const [count, setCount] = atom(0)
const [increment, setIncrement] = atom(1)
reactive(() => {
console.log(`count is ${count() + nonreactive(increment)}`)
})
await setCount(current => 1) // triggers the reactive function and logs `count is 2`
await setIncrement(2) // does not trigger the reactive function
await setCount(current => 2) // triggers the reactive function and logs `count is 4`
guard
- src/hooks.mjs#57
- Accept
Function
,Function
- Return
Any
import { guard } from "atomi"
Used as wrapper around an atom or a function to access its value, but triggers value changes. Acceps a comparison function as a second argument.
| Argument | Type | Desc |
|---|:---|:---|
| Callback | Function
| Getter function or atomic function |
| Comparator | Function
| Comparator function; accepts 2 arguments, new and old return value of the callback function. Will trigger the dependencies and store the new value when Comparator returns true
|
Returns the return value of the callback function
const [number, setNumber] = atom(0)
reactive(() => {
console.log(`number is ${guard(number)}`)
})
await setNumber(1) // triggers the reactive function and logs `count is 1`
await setNumber(1) // does not trigger the reactive function
await setNumber(2) // triggers the reactive function and logs `count is 2`
Example using custom comparator function:
const [number, setNumber] = atom(0)
reactive(() => {
console.log(`number is ${guard(number, (a, b) => parseInt(a) !== parseInt(b))}`)
})
await setNumber(0.5) // does not trigger the reactive function, because parseInt(0.5) is still 0
await setNumber(1.1) // triggers the reactive function and logs `count is 1.1`
await setNumber(1.5) // does not trigger the reactive function, because parseInt(1.5) is still 1
Core
This part describes the core functionality that the framework is built upon. Majority of users should not interact with it directly unless working on a library that extends core functionalities.
ReactiveVar
- src/core.mjs#120
- Accept
Any
- Return instance of RectiveVar
A class that is used by atoms internally to store value.
NOTE: it is recommended to use atoms in your work, unless you are working on a library or you like the syntax of it better. If you prefer to use
ReactiveVar
directly for OOP style programming, you still should use new atom instead
import { ReactiveVar } from "atomi"
#get
Returns current value of the ReactiveVar and registers current scope as a dependency is current scope exists
const counter = new atom(0)
reactive(() => {
const count = counter.get()
...
})
#set
Sets value of the ReactiveVar
const counter = new atom(0)
counter.set(2) // counter.get will return 2 instead
Scope
- src/core.mjs#45
- Accept
Function
- Return instance of Scope
A class that manages reactive functions
import { Scope } from "atomi"
| Argument | Type | Desc |
|---|:---|:---|
| Callback | Function
| The function that we with to execute when inside update |
reactive passes scope to the callback fucntion and returns the same scope
const scope = reactive(scope => {
console.log(scope.firstRun) // logs: true
})
scope.die()
#depend
- src/core.mjs#63
- Return
void
Is used by ReactiveVar.get to register a dependency
die
- src/core.mjs#98
- Return
void
Intended to be used like #stop but permanently
#execute
- src/core.mjs#78
- Return
void
Is used by Tracker when to execute reactive function when Scope was triggered by any ReactiveVars previously
#firstRun
- src/core.mjs#59
- Return
Boolean
A getter property of Scope. Returns true if the dependant function has not been called more then once.
reactive(scope => {
console.log(scope.firstRun) // logs: true
})
#resume
- src/core.mjs#73
- Return
void
Is used to restore connection between dependant ReactiveVars and the Scope. Should be called to restore reactive function execution after Scope.stop had been called.
const [count, setCount] = atom(0)
const scope = reactive(scope => {
console.log(`count is ${count()}`)
})
await setCount(1) // logs `count is 1`
scope.stop()
await setCount(2) // does not log anything
scope.resume()
await setCount(3) // logs `count is 3`
#stop
- src/core.mjs#68
- Return
void
Is used when we want to temporary suspend reactive function execution event when its dependencies are updated.
const [count, setCount] = atom(0)
const scope = reactive(scope => {
console.log(`count is ${count()}`)
})
await setCount(1) // logs `count is 1`
scope.stop()
await setCount(2) // does not log anything
scope.resume()
await setCount(3) // logs `count is 3`
#trigger
- src/core.mjs#109
- Return
Promise
Is used by ReactiveVar when setting a new value.
Returns Promise
that fullfils when reactive function of the scope is finished executing
#triggeredBy
- src/core.mjs#52
- Type
Set
Is a property of the scope. Is usefull when debugging to know what ReactiveVar triggered the scope update when scope depends on multiple ReactiveVars
Tracker
- src/core.mjs#2
- Type
Object
Intended to be used as a global object to track and register ReactiveVar updates and execute reactive functions
import { Tracker } from "atomi"
scheduleJob
- src/core.mjs#5
- Accept scope
- Return
Promise
Schedules scope's reactive function to execute when callback queue is empty. Is used by Scope.trigger to schedule function execution when dependant ReactiveVars update.
Usefull functions
This sections will focus exclusively on the functions to enhance experience with using atoms Specifically on the (callback setter)[#callback-setter) part of it. All of the following functions shine when used in combination with the callback setter
Numbers
add
- src/numbers.mjs#2
- Accept
Number
- Return
(Number) => Number
Creates and adder function
import { add } from "atomi"
const [count,, setCount] = atom(0)
setCount(add(2)) // sets 2 as count value
setCount(add(5)) // sets 7 as count value
sub
- src/numbers.mjs#6
- Accept
Number
- Return
(Number) => Number
Creates and subtractor function
import { sub } from "atomi"
const [count,, setCount] = atom(10)
setCount(sub(2)) // sets 8 as count value
setCount(sub(5)) // sets 3 as count value
inc
- src/numbers.mjs#10
- Accept
(Number) => Number
- Return
Number
Is an edge case of add function that uses 1 as its argument
import { inc } from "atomi"
const [count,, setCount] = atom(0)
setCount(inc) // sets 1 as count value
setCount(inc) // sets 2 as count value
dec
- src/numbers.mjs#11
- Accept
(Number) => Number
- Return
Number
Is an edge case of sub function that uses 1 as its argument
import { sub } from "atomi"
const [count,, setCount] = atom(10)
setCount(sub) // sets 9 as count value
setCount(sub) // sets 8 as count value
power
- src/numbers.mjs#13
- Accept
Number
- Return
(Number) => Number
Creates power function
import { pow } from "atomi"
const [count,, setCount] = atom(2)
setCount(pow(2)) // sets 4 as count value
setCount(pow(3)) // sets 64 as count value
Objects
assign
- src/objects.mjs#2
- Accept
Object
- Return
(Object) => Object
Creates assigner function
import { assign } from "atomi"
const [person,, setPerson] = atom({ name: "tim" })
setPerson(assing({ age: 22 })) // sets person to be { name: "tim", age: 22 }
setPerson(assing({ age: 23 })) // sets person to be { name: "tim", age: 23 }
Booleans
not
- src/booleans.mjs#2
- Accept
Any
- Return
Boolean
Should be self explanatory
import { not } from "atomi"
const [state,, setState] = atom(true)
setState(not) // sets state to false
setState(not) // sets state to true
id
- src/booleans.mjs#6
- Accept
Any
- Return
Any
Good old identity function
import { id } from "atomi"
const [array,, setArray] = atom([1,0,2,0])
setArray(filter(id)) // filters out all falsy values leaving [1, 2]
is
- src/booleans.mjs#10
- Accept
Any
- Return
(Any) => Boolean
Good old identity function
import { is } from "atomi"
const [array,, setArray] = atom([1,0,1,2,0])
setArray(filter(is(1))) // filters out all values that are not 1 leaving [1, 1]
lesser
- src/booleans.mjs#14
- Accept
Number
- Return
(Number) => Boolean
Creates function that check if value is lesser then provided
import { lesser } from "atomi"
const [array,, setArray] = atom([1,0,1,2,0])
setArray(filter(lesser(2))) // filters all values that less then 2 [1,0,1,0]
greater
- src/booleans.mjs#18
- Accept
Number
- Return
(Number) => Boolean
Creates function that check if value is lesser then provided
import { greater } from "atomi"
const [array,, setArray] = atom([1,0,1,2,0])
setArray(filter(greater(1))) // filters all values that are greater then 1 leaving [2]
negative
- src/booleans.mjs#22
- Accept
Number
- Return
Boolean
Is an edge case of lesser function that uses 1 as its argument
import { negative } from "atomi"
const [array,, setArray] = atom([-1,0,1,2,0])
setArray(filter(negative)) // filters out all values that are not negative leaving [-1]
positive
- src/booleans.mjs#23
- Accept
Number
- Return
Boolean
Is an edge case of greater function that uses 1 as its argument
import { positive } from "atomi"
const [array,, setArray] = atom([-1,0,1,2,0])
setArray(filter(positive)) // filters out all values that are not negative leaving [1,2]
Arrays
map
- src/arrays.mjs#2
- Accept
(Any) => Any
- Return
(Any) => Any
Creates a mapper
import { map } from "atomi"
const [array,, setArray] = atom([1,3,2])
setArray(map(x => x + 1)) // increases all array elements by 1
setArray(map(inc)) // increases all array elements by 1 using inc
filter
- src/arrays.mjs#6
- Accept
(Any) => Any
- Return
(Any) => Any
Creates a filterer
import { filter } from "atomi"
const [array,, setArray] = atom([1,3,2])
setArray(filter(x => x > 1)) // keeps only elemens that are larger then 1
prepend
- src/arrays.mjs#10
- Accept
Any
- Return
(Array) => Array
Creates a prepender
import { prepend } from "atomi"
const [array,, setArray] = atom([1,3,2])
setArray(prepend(0, 0.5)) // adds 0 and 0.5 at the beginning. Not array is [0,0.5,1,3,2]
append
- src/arrays.mjs#14
- Accept ...
Any
- Return
(Array) => Array
Creates a appender
import { append } from "atomi"
const [array,, setArray] = atom([1,3,2])
setArray(append(0)) // add 0 at the end. Not array is [1,3,2,0]
insert
- src/arrays.mjs#18
- Accept
Number
, ...Any
- Return
(Array) => Array
Creates an inserter
import { insert } from "atomi"
const [array,, setArray] = atom([1,3,2])
setArray(insert(1, 4, 5)) // inserted [4, 5] at position 1. Now array stores [1, 4, 5, 3, 2]
assignWhere
- src/arrays.mjs#26
- Accept
(Any) => Boolean
,Any => Any
- Return
(Array, NONE) => Array
Creates a function that will update specific element in the array
import { assignWhere } from "atomi"
const [array,, setArray] = atom([1,3,2])
setArray(assignWhere(is(3)), inc) // where element is equal to 3 it will increases it by 1. Now array stores [1,4,2]
sort
- src/arrays.mjs#37
- Accept
(Any, Any) => Boolean
- Return
(Array) => Array
Creates a function that will sort an array based on the sorter provided when passed to a callback setter
import { sort } from "atomi"
const [array,, setArray] = atom([11,1,3,2])
setArray(sort()) // using default js sorter. Now array stores [1, 11, 2, 3]
asc
- src/arrays.mjs#44
- Accept
Any
,Any
- Return
Boolean
Precoded sorter to sort by ascending order
import { asc } from "atomi"
const [array,, setArray] = atom([11,1,3,2])
setArray(sort(asc)) // using default js sorter. Now array stores [1, 2, 3, 11]
desc
- src/arrays.mjs#48
- Accept
(Any, Any) => Boolean
- Return
(Array) => Array
Precoded sorter to sort by descending order
import { desc } from "atomi"
const [array,, setArray] = atom([11,1,3,2])
setArray(sort(desc)) // using default js sorter. Now array stores [11, 3, 2, 1]
Optimizing
omap
Creates a function reacting to changes in an atom containing an array keeping track of previous changes, calling a map callback only when the element mutates.
import { omap } from "atomi"
const [array,, setArray] = atom([11, 1, 2, 3])
const result = omap(array, x => x + 1)
console.log(result()) // [12, 2, 3, 4]
await setArray(arr => { arr[0] = 0; return arr; })
// NOTE: map fn `x => x + 1` was called only once with 0;
console.log(result()); // [1, 2, 3, 4]
await setArray(arr => { arr[2] = 3; arr[3] = 2; return arr; })
// NOTE: map fn `x => x + 1` was not called at all because elements just swapped places,
// but result has been updated
console.log(result()); // [1, 2, 4, 3]
omapEnumerated
Creates a function reacting to changes in an atom containing an array keeping track of previous changes, calling a map callback only when the element mutates or changes its potisition in the array.
import { omapEnumerated } from "atomi"
const [array,, setArray] = atom([0, 1, 2, 3])
const result = omapEnumerated(array, (x, index) => x + index)
console.log(result()) // [0, 2, 4, 6]
await setArray(arr => { arr[3] = 4; return arr; })
// NOTE: map fn `x => x + 1` was called only once with 4;
console.log(result()); // [0, 2, 4, 7]
await setArray(arr => { arr[2] = 3; arr[3] = 2; return arr; })
// NOTE: map fn `x => x + 1` will be called twice for 3 and 2 with respective indexes
console.log(result()); // [0, 2, 5, 5]