karet.util
v0.19.2
Published
Utilities for working with Karet
Downloads
473
Maintainers
Readme
≡ ▶ Karet Util ·
A collection of utilities for working with Karet.
≡ ▶ Contents
- Reference
- About lifted functions
- Debugging
- Atoms
- Bus
- Convenience
- React helpers
- Kefir
- Algebras
- Conditionals
- Animation
- Lifting
- Curried combinators
U.changes(observable)
⌘U.debounce(ms, observable)
⌘U.delay(ms, observable)
⌘U.flatMapErrors(value => observable, observable)
⌘U.flatMapLatest(value => observable, observable)
⌘U.flatMapParallel(value => observable, observable)
⌘U.flatMapSerial(value => observable, observable)
⌘U.foldPast((state, value) => state, state, observable)
⌘U.fromEvents(target, eventName, transform)
⌘U.ignoreErrors(observable)
⌘U.ignoreValues(observable)
⌘U.interval(ms, value)
⌘U.later(ms, value)
⌘U.mapValue(value => value, observable)
⌘U.never
⌘U.parallel([...observables])
⌘U.sampledBy(eventStream, property)
⌘U.serially([...observables])
⌘U.skipDuplicates(equality, observable)
⌘U.skipFirst(number, observable)
⌘U.skipFirstErrors(n, observable)
⌘U.skipUnless(predicate, observable)
⌘U.takeFirst(number, observable)
⌘U.takeFirstErrors(number, observable)
⌘U.takeUntilBy(eventStream, observable)
⌘U.throttle(ms, observable)
⌘U.toObservable(observable)
⌘U.toProperty(observable)
⌘
- Additional combinators
- Subscribing
- Lifted standard functions
- JSON
- URIs
- Math
U.abs(x)
⌘U.acos(x)
⌘U.acosh(x)
⌘U.asin(x)
⌘U.asinh(x)
⌘U.atan(x)
⌘U.atanh(x)
⌘U.atan2(y, x)
⌘U.cbrt(x)
⌘U.ceil(x)
⌘U.clz32(x)
⌘U.cos(x)
⌘U.cosh(x)
⌘U.exp(x)
⌘U.expm1(x)
⌘U.floor(x)
⌘U.fround(x)
⌘U.hypot(...xs)
⌘U.imul(x, y)
⌘U.log(x)
⌘U.log1p(x)
⌘U.log10(x)
⌘U.log2(x)
⌘U.max(...xs)
⌘U.min(...xs)
⌘U.pow(x, y)
⌘U.round(x)
⌘U.sign(x)
⌘U.sin(x)
⌘U.sinh(x)
⌘U.sqrt(x)
⌘U.tan(x)
⌘U.tanh(x)
⌘U.trunc(x)
⌘
- String
≡ ▶ Reference
This library provides a large number of named exports. Typically one just imports the library as:
import * as U from 'karet.util'
≡ ▶ About lifted functions
Many of the functions in this library are lifted so that they accept both ordinary values and observables as inputs. When such functions are given only ordinary values as inputs, they return immediately with the result value. OTOH, when such a function is given at least an observable as an input, they return an observable of results.
≡ ▶ Debugging
≡ ▶ U.show(...labels, any)
U.show
is basically an identity function that console.log
s the values
flowing through. U.show
works on plain values, observables, and atoms. When
used with atoms, U.show
also logs values written through.
For example:
const Component = ({state}) => (
<div>
<SubComponent subState={U.show('subState', U.view('subState', state))}/>
</div>
)
≡ ▶ Atoms
≡ ▶ Creating atoms
≡ ▶ U.atom(value)
U.atom
creates a new atom with the given initial value.
For example:
const notEmpty = U.atom('initial')
notEmpty.get() // 'initial'
notEmpty.log() // [property] <value:current> initial
≡ ▶ U.molecule([ ... ] | { ... })
U.molecule
composes an atom from a template of atoms.
For example:
const xyA = U.atom({x: 1, y: 2})
const xL = U.view('x', xyA)
const yL = U.view('y', xyA)
const xyM = U.molecule({x: xL, y: yL})
When read, either as a property or via get
, the atoms in the template are
replaced by their values:
R.equals( xyM.get(), xyA.get() ) // true
When written to, the atoms in the template are written to with matching elements from the written value:
xyM.modify(L.set('x', 3))
xL.get() // 3
yL.get() // 2
The writes are performed holding event propagation.
It is considered an error, and the effect is unpredictable, if the written value
does not match the template, aside from the positions of abstract mutables, of
course, which means that write operations, set
, remove
, and modify
, on
molecules and lensed atoms created from molecules are only partial.
Also, if the template contains multiple abstract mutables that correspond to the same underlying state, then writing through the template will give unpredictable results.
≡ ▶ U.variable()
U.variable
creates a new atom without an initial value. See also
U.refTo
.
For example:
const empty = U.variable()
empty.get() // undefined
empty.log()
empty.set('first') // [property] <value> first
≡ ▶ Transactions
≡ ▶ U.holding(() => { ... })
U.holding
is given a thunk to call while holding the propagation of events
from changes to atoms. The thunk can get
, set
, remove
and modify
any
number of atoms. After the thunk returns, persisting changes to atoms are
propagated. See also U.actions
and U.getProps
.
For example:
const xy = U.atom({x: 1, y: 2})
const x = U.view('x', xy)
const y = U.view('y', xy)
x.log('x') // x <value:current> 1
y.log('y') // y <value:current> 2
U.holding(() => {
xy.set({x: 2, y: 1})
x.set(x.get() - 1)
}) // y <value> 1
≡ ▶ Decomposing
≡ ▶ U.destructure(atom)
U.destructure
wraps a given atom or observable with a
proxy
that performs property access via U.view
. On plain observable
properties only get
access is supported. On mutable atoms get
, set
, and
deleteProperty
accesses are supported.
For example,
const {name, number} = U.destructure(contact)
is equivalent to
const name = U.view('name', contact)
const number = U.view('number', contact)
Note that all property accesses through the proxy returned by U.destructure
are performed via U.view
. This means that the return value of
U.destructure
cannot be used as the atom or observable that it proxies.
Note that U.destructure
is not recursive, which means that nested
destructuring cannot be used. Only single level property access is proxied.
Note that U.destructure
requires proper
Proxy
support. You need to decide whether you can use
it.
≡ ▶ U.mapElems((elemAtom, index) => any, arrayAtom)
U.mapElems
performs a cached incremental map over state containing an array of
values. On changes, the mapping function is only called for elements that were
not in the state previously. U.mapElems
can be used for rendering a list of
stateless components, however, if elements in the array have unique ids, then
U.mapElemsWithIds
is generally preferable.
For example:
const Contacts = ({contacts}) => (
<div>
{U.mapElems((contact, i) => <Contact {...{key: i, contact}} />, contacts)}
</div>
)
See the live Contacts CodeSandbox for an example.
≡ ▶ U.mapElemsWithIds(idLens, (elemAtom, id) => any, arrayAtom)
U.mapElemsWithIds
performs a cached incremental map over state containing an
array of values with unique ids. On changes, the mapping function is only
called for elements that were not in the state previously. U.mapElemsWithIds
is particularly designed for rendering a list of potentially stateful
components efficiently. See also U.mapElems
.
For example:
const state = U.atom([
{id: 1, value: 'Keep'},
{id: 2, value: 'Calmm'},
{id: 3, value: 'and'},
{id: 4, value: 'React!'}
])
const Elems = ({elems}) => (
<ul>
{U.mapElemsWithIds(
'id',
(elem, id) => <li key={id}>{U.view('value', elem)}</li>,
elems
)}
</ul>
)
U.mapElemsWithIds
is asymptotically optimal in the sense that any change (new
elements added or old elements removed, positions of elements changed, ...) has
Theta(n)
complexity. That is the best that can be achieved with plain arrays.
≡ ▶ U.view(lens, atom)
U.view
creates a read-write view with the given lens from the given original
atom. The lens may also be an observable producing lenses. Modifications to
the lensed atom are reflected in the original atom and vice verse.
For example:
const root = U.atom({x: 1})
const x = U.view('x', root)
x.set(2)
root.get() // { x: 2 }
root.set({x: 3})
x.get() // 3
One of the key ideas that makes lensed atoms work is the compositionality of
partial lenses. Those
equations make it possible not just to create lenses via composition (left hand
sides of equations), but also to create paths of lensed atoms (right hand sides
of equations). More concretely, both the c
in
const b = U.view(a_to_b_PLens, a)
const c = U.view(b_to_c_PLens, b)
and in
const c = U.view([a_to_b_PLens, b_to_c_PLens], a)
can be considered equivalent thanks to the compositionality equations of lenses.
Note that, for most intents and purposes, U.view
is a referentially
transparent function:
it does not create new mutable state—it merely creates a reference to
existing mutable state.
U.view
is the primary means of decomposing state for sub-components. For
example:
const Contact = ({contact}) => (
<div>
<TextInput value={U.view('name', contact)} />
<TextInput value={U.view('phone', contact)} />
</div>
)
See the live Contacts CodeSandbox for an example.
≡ ▶ Side-effects on atoms
≡ ▶ U.set(atom, value)
U.set
sets the given value to the specified atom. In case the value is
actually an observable, U.set
returns a sink that sets any values
produced by the observable to the atom.
For example:
const Component = ({parameters}) => {
const state = U.atom(null)
// ...
return (
<div>
{U.set(state, httpRequestAsObservable(parameters))}
{U.ifElse(
R.isNil(state),
<Spinner />,
<Editor state={state} />
)}
</div>
)
}
Note that the above kind of arrangement to fetch data and set it into an atom is not needed when the data is only displayed in a read-only fashion in the UI.
≡ ▶ Actions on atoms
≡ ▶ U.doModify(atom, mapper)
U.doModify
creates an action that invokes the modify
method on the given
atom with the given mapping function.
≡ ▶ U.doRemove(atom)
U.doRemove
creates an action that invokes the remove
method on the given
atom.
≡ ▶ U.doSet(atom, value)
U.doSet
creates an action that invokes the set
method on the given atom with
the given value.
≡ ▶ Bus
≡ ▶ Creating buses
≡ ▶ U.bus()
U.bus()
creates a new observable Bus
stream. A Bus
stream has the
following methods:
bus.push(value)
to explicitly emit valuevalue
,bus.error(error)
to explicitly emit errorerror
, andbus.end()
to explicitly end the stream after which all the methods do nothing.
Note that buses and event streams, in general, are fairly rarely used with Karet. They can be useful for performing IO actions and in cases where actions from UI controls need to be throttled or combined.
See the live Counter using Event Streams CodeSandbox for an example.
≡ ▶ U.serializer(initial[, [...actions]])
U.serializer
creates a new observable property for serially executing
actions, which are zero argument functions that may return a value or an
observable that should eventually end. The returned property starts with the
given initial
value and then continues with the results of the optional array
of actions
. Like a Bus
, the returned property also has the
following extra methods:
bus.push(action)
to explicitly push a new action to be executed,bus.error(error)
to explicitly emit errorerror
, andbus.end()
to explicitly stop the serializer after which all the methods do nothing.
The property must be subscribed to in order to process actions.
See the Form using Context CodeSandbox for a minimalist example that uses a serializer to execute a simulated asynchronous operation.
≡ ▶ Actions on buses
≡ ▶ U.doEnd(bus)
U.doEnd
creates an action that invokes the end
method on the given bus.
≡ ▶ U.doError(bus, error)
U.doError
creates an action that invokes the error
method on the given bus
with the given value.
≡ ▶ U.doPush(bus, value)
U.doPush
creates an action that invokes the push
method on the given bus
with the given value.
≡ ▶ Convenience
≡ ▶ U.scope((...) => ...)
U.scope
simply calls the given thunk. IOW, U.scope(fn)
is equivalent to
(fn)()
. You can use it to create a new scope at expression level.
For example:
U.scope((x = 1, y = 2) => x + y)
// 3
≡ ▶ U.tapPartial(action, any)
U.tapPartial
is a lifted partial
tap function. The given action is called for
values flowing through except when the value is undefined
.
For example:
U.thru(
observable,
...
U.tapPartial(value => console.log(value)),
...
)
≡ ▶ U.thru(any, ...fns)
U.thru
allows one to pipe a value through a sequence of functions. In other
words, U.thru(x, fn_1, ..., fn_N)
is roughly equivalent to fn_N( ... fn_1(x)
... )
. It serves a similar purpose as the
->>
macro of Clojure or the
|>
operator of
F#
and Elm,
for example, or the
>|
operator defined in a Usenet post by some rando. See also
U.through
.
For example:
U.thru(1, x => x + 1, x => -x)
// -2
A common technique in JavaScript is to use method chaining: x.fn_1().fn_2()
.
A problem with method chaining is that it requires having objects with methods.
Sometimes you may need to manipulate values that are not objects, like null
and undefined
, and other times you may want to use functions that are not
directly provided as methods and it may not be desirable to monkey
patch such methods.
U.thru
is designed to work with partially applied
curried and lifted
functions that take the object as their last
argument and can be seen as providing a flexible alternative to method chaining.
≡ ▶ U.through(...fns)
U.through
allows one to compose a function that passes its single argument
through all of the given functions from left to right. In other words,
U.through(fn_1, ..., fn_N)(x)
is roughly equivalent to fn_N( ... fn_1(x)
... )
. It serves a similar purpose as
R.pipe
, but has been crafted to work with
lifted functions. See also U.thru
.
For example:
U.through(x => x + 1, x => -x)(1)
// -2
≡ ▶ U.toPartial(fn)
U.toPartial
takes the given function and returns a curried version of the
function that immediately returns undefined
if any of the arguments passed is
undefined
and otherwise calls the given function with arguments.
For example:
U.toPartial((x, y) => x + y)(1, undefined)
// undefined
U.toPartial((x, y) => x + y)(1, 2)
// 3
≡ ▶ React helpers
≡ ▶ Binding
≡ ▶ U.getProps({...propName: atom|bus})
U.getProps
returns an event callback that gets the values of the properties
named in the given template from the event target and pushes or sets them to the
buses or atoms that are the values of the properties. In case
the template contains multiple properties, the results are written while
holding change propagation.
For example:
const TextInput = ({value}) => (
<input type="text" value={value} onChange={U.getProps({value})} />
)
const Checkbox = ({checked}) => (
<input type="checkbox" checked={checked} onChange={U.getProps({checked})} />
)
≡ ▶ U.setProps({...propName: observable})
U.setProps
returns a callback designed to be used with ref
that subscribes
to observables in the given template and copies values from the observables to
the named properties in the DOM element. This allows one to bind to DOM
properties such as scroll position that are not HTML attributes. See also
U.actions
.
See the live Scroll CodeSandbox for an example.
≡ ▶ Input components
≡ ▶ <U.Input {...{value|checked}} />
U.Input
is a wrapper for an input
element that binds either
onChange={U.getProps({value})}
or
onChange={U.getProps({checked})}
when either value
or
checked
is a defined property.
For example:
const checked = U.atom(false)
// ...
<U.Input type="checkbox" checked={checked} />
≡ ▶ <U.Select {...{value}} />
U.Select
is a wrapper for a select
element that binds
onChange={U.getProps({value})}
when value
is a defined
property.
For example:
const value = U.atom('')
// ...
<U.Select value={value} />
≡ ▶ <U.TextArea {...{value}} />
U.TextArea
is a wrapper for a textarea
element that binds
onChange={U.getProps({value})}
when value
is a defined
property.
For example:
const value = U.atom('')
// ...
<U.TextArea value={value} />
≡ ▶ Refs
≡ ▶ U.refTo(variable)
U.refTo
is designed for getting a reference to the DOM element of a component.
See also U.variable
. See also U.actions
.
For example:
const Component = ({dom = U.variable()}) => (
<div ref={U.refTo(dom)}>
...
</div>
)
React calls the
ref
callback with the DOM element on mount and with null
on unmount.
However, U.refTo
does not write null
to the variable. The upside of
skipping null
and using an initially empty variable rather than
an atom is that once the variable emits a value, you can be sure that
it refers to a DOM element.
Note that in case you also want to observe null
values, you can use
U.set
instead of U.refTo
:
const Component = ({dom = U.variable()}) => (
<div ref={U.set(dom)}>
...
</div>
)
≡ ▶ Lifecycle
≡ ▶ U.onUnmount(action)
U.onUnmount
allows you to perform an action when a component is unmounted.
For example:
const Component = () => {
console.log('Mounted!')
return (
<div>
{U.onUnmount(() => console.log('Unmounted!'))}
</div>
)
}
≡ ▶ Events
≡ ▶ U.actions(...actions)
U.actions
is designed for creating an action from multiple actions. It
returns an unary action function that calls the functions in the arguments with
the same argument. In case there are multiple actions, they are performed while
holding change propagation.
For example:
const InputValue = ({value, onChange}) => (
<input value={value} onChange={U.actions(onChange, U.getProps({value}))} />
)
Note that in case onChange
is not given to the above component as a property
it causes no problem as U.actions
does not attempt to call undefined
.
Note that U.actions
can also be used with actions given to the React ref
property.
≡ ▶ U.preventDefault(event)
U.preventDefault
invokes the preventDefault
method on the given object.
≡ ▶ U.stopPropagation(event)
U.stopPropagation
invokes the stopPropagation
method on the given object.
≡ ▶ Class names
≡ ▶ U.cns(...classNames)
U.cns
is designed for creating a list of class names for the className
property. It joins the truthy values from the arguments with a space. In case
the result would be an empty string, undefined
is returned instead.
For example:
const Component = ({className}) => (
<div className={U.cns(className, 'a-class-name', false, 'another-one', undefined)} />
)
≡ ▶ Interop
≡ ▶ U.pure(Component)
U.pure
wraps the given component inside a
PureComponent
.
U.pure
can be used for preventing unnecessary rerenders when a Karet component
is used as a child of a React component that rerenders its children even when
their props do not change. See also U.toReact
.
≡ ▶ U.toKaret(ReactComponent)
U.toKaret
converts a React component that takes plain value properties to a
Karet component that can be given observable properties. U.toKaret
is useful
when using React components, such as React
Select, as children of Karet
components and with observable rather than plain value properties. U.toKaret
is a synonym for fromClass
from
the Karet library.
≡ ▶ U.toReact(KaretComponent)
U.toReact
converts a Karet component that expects observable properties and
should not be rerendered unnecessarily into a React component that takes plain
value properties. U.toReact
may be needed particularly when a Karet component
is controlled by a higher-order React component, such as React
Router, because Karet components
typically (are not and) must not be rerendered unnecessarily. U.toReact
is
equivalent to U.toReactExcept(() => false)
. See also
U.pure
.
≡ ▶ U.toReactExcept(propName => boolean, KaretComponent)
U.toReactExcept
converts a Karet component that expects observable properties
and should not be rerendered unnecessarily into a React component that takes
plain value properties. The given predicate is used to determine which
properties must not be converted to observable properties. Like
U.toReact
, U.toReactExcept
may be needed particularly when a
Karet component is controlled by a higher-order React component, such as React
Router, because Karet components
typically (are not and) must not be rerendered unnecessarily. See the Calmm
function to React class CodeSandbox for an
example.
≡ ▶ Kefir
≡ ▶ Algebras
≡ ▶ U.IdentityLatest
U.IdentityLatest
is a Static Land compatible algebra module over properties,
like U.Latest
, or plain values.
≡ ▶ U.Latest
U.Latest
is a Static Land compatible algebra module over properties from which
only the latest value is propagated. Currently it implements the
monad
algebra.
For example:
log(
L.traverse(
U.Latest,
x => U.startWith(undefined, U.later(x*1000, x)),
L.elems,
[1, 2, 3]
)
)
≡ ▶ Conditionals
≡ ▶ U.and(...any)
U.and
is a lazy variadic logical and over potentially observable properties.
U.and(l, r)
does not subscribe to r
unless l
is truthy.
≡ ▶ U.cond(...[condition, consequent][, [alternative]])
U.cond
allows one to express a sequence of conditionals. U.cond
translates
to a nested expression of U.ifElse
s.
U.cond( [ condition, consequent ]
, ...
[ , [ alternative ] ] )
The last [ alternative ]
, which, when present, needs to be a singleton array,
is optional.
≡ ▶ U.ifElse(condition, consequent, alternative)
U.ifElse
is basically an implementation of the conditional operator condition
? consequent : alternative
for observable properties.
U.ifElse(condition, consequent, alternative)
is roughly shorthand for
U.toProperty(
U.flatMapLatest(boolean => (boolean ? consequent : alternative), condition)
)
except that the consequent
and alternative
expressions are only evaluated
once.
≡ ▶ U.not(any)
U.not
is a logical negation over a potentially observable property.
≡ ▶ U.or(...any)
U.or
is a lazy variadic logical or over potentially observable properties.
U.or(l, r)
does not subscribe to r
unless l
is falsy.
≡ ▶ U.unless(condition, alternative)
U.unless(condition, alternative)
is shorthand for U.ifElse(condition,
undefined, alternative)
.
≡ ▶ U.when(condition, consequent)
U.when(condition, consequent)
is shorthand for U.ifElse(condition,
consequent, undefined)
.
≡ ▶ Animation
≡ ▶ U.animationSpan(milliseconds)
U.animationSpan
creates a property of increasing values from 0 to 1 for the
given duration in milliseconds on each animation frame as generated by
requestAnimationFrame
.
See the live Animation CodeSandbox for an example.
≡ ▶ Lifting
≡ ▶ U.combine([...any], fn)
U.combine
is a special purpose Kefir
observable property combinator designed for combining properties for a sink that
accepts both observable properties and constant values such as the Reactive VDOM
of Karet.
Unlike typical property combinators, when U.combine
is invoked with only
constants (no properties), then the result is computed immediately and returned
as a plain value. This optimization eliminates redundant properties.
The basic semantics of U.combine
can be described as
U.combine(xs, fn) === Kefir.combine(xs, fn).skipDuplicates(R.identical)
where Kefir.combine
and
skipDuplicates
come from
Kefir and R.identical
from
Ramda. Duplicates are skipped, because that can reduce
unnecessary updates. Ramda's R.identical
provides a semantics of equality
that works well within the context of embedding properties to VDOM.
Unlike with Kefir.combine
, any of
the argument xs
given to U.combine
is allowed to be
- a constant,
- a property, or
- a data structure of nested arrays and plain objects containing properties.
In other words, U.combine
also provides functionality similar to
Bacon.combineTemplate
.
Note: U.combine
is carefully optimized for space—if you write equivalent
combinations using Kefir's own operators, they will likely take more memory.
≡ ▶ U.lift((...) => ...)
U.lift
allows one to lift a function operating on plain values to a function
that operates both on plain values and on observable properties. When given
only plain values, the resulting function returns a plain value. When given
observable properties, the resulting function returns an observable property of
results. See also U.liftRec
For example:
const includes = U.lift( (xs, x) => xs.includes(x) )
const obsOfBooleans = includes(obsOfArrays, obsOfValues)
U.lift
works well for simple functions that do not return functions. If you
need to lift higher-order functions that return new functions that should also
be lifted, try U.liftRec
.
≡ ▶ U.liftRec((...) => ...)
U.liftRec
allows one to lift a function operating on plain values to a
function that operates both on plain values and on observable properties. When
given only plain values, the resulting function returns a plain value. When
given observable properties, the resulting function returns an observable
property of results. See also U.lift
.
For example:
const either = U.liftRec(R.either)
const equals = U.lift(R.equals)
const obsOfBooleans = either(R.equals(obs1), R.equals(obs2))
U.liftRec
is designed to be very simple to use. For example, the Kefir
Ramda
library simply wraps every Ramda function with liftRec
and this results in a library that has essentially the same signature (currying
and variable argument functions work the same) as Ramda except that the
functions also work on Kefir observables.
≡ ▶ Curried combinators
Kefir is a traditional JavaScript library that provides a fluent API using method chaining. Karet Util supports more functional style JavaScript by providing curried functions for programming with Kefir. The functions provided by Karet Util also try to avoid constructing observables unnecessarily.
The following are simply links to the relevant Kefir documentation:
U.changes(observable)
⌘U.debounce(ms, observable)
⌘U.delay(ms, observable)
⌘U.flatMapErrors(value => observable, observable)
⌘U.flatMapLatest(value => observable, observable)
⌘U.flatMapParallel(value => observable, observable)
⌘U.flatMapSerial(value => observable, observable)
⌘U.foldPast((state, value) => state, state, observable)
⌘U.fromEvents(target, eventName, transform)
⌘U.ignoreErrors(observable)
⌘U.ignoreValues(observable)
⌘U.interval(ms, value)
⌘U.later(ms, value)
⌘U.mapValue(value => value, observable)
⌘U.never
⌘ — Note that this is not a function!U.parallel([...observables])
⌘U.sampledBy(eventStream, property)
⌘U.serially([...observables])
⌘U.skipDuplicates(equality, observable)
⌘U.skipFirst(number, observable)
⌘U.skipFirstErrors(n, observable)
⌘U.skipUnless(predicate, observable)
⌘U.takeFirst(number, observable)
⌘U.takeFirstErrors(number, observable)
⌘U.takeUntilBy(eventStream, observable)
⌘U.throttle(ms, observable)
⌘U.toObservable(observable)
⌘U.toProperty(observable)
⌘
≡ ▶ Additional combinators
≡ ▶ U.consume(action, observable)
U.consume
creates a property that simply immediately produces undefined
and
subscribes to the given observable whose values it passes to the given action
for as long as the returned property is subscribed to. U.consume
can be used
for executing side-effects during the time that a component is mounted. See
also U.sink
.
For example:
const DocumentTitle = ({title}) => (
<React.Fragment>
{U.consume(title => {
if (typeof document !== 'undefined') document.title = title
}, title)}
</React.Fragment>
)
≡ ▶ U.endWith(value, observable)
U.endWith
creates an observable that ends with the given value. That is,
after the given observable ends, the given value is emitted.
≡ ▶ U.lazy(() => observable)
U.lazy
allows to create an observable lazily.
For example, one use case for U.lazy
is to create cyclic observables:
const sequence = ['⠋', '⠙', '⠸', '⠴', '⠦', '⠇']
const loop = () =>
U.serially([U.serially(sequence.map(U.later(100))), U.lazy(loop)])
See the live Login CodeSandbox for an example.
≡ ▶ U.fromPromise(() => promise | {ready, abort})
U.fromPromise
converts a thunk that returns a promise or an object of the
shape {ready, abort}
where ready
is a promise and abort
is an action that
aborts the promise into a Kefir property. The thunk is invoked once when the
property is subscribed to for the first time. If an abort
action is defined
and all subscriptions of the property are closed before the promise resolves,
the property is ended and the abort
action is called once.
For example:
const fetchJSON =
typeof AbortController === 'undefined'
? (url, params = {}) =>
U.fromPromise(() => fetch(url, params).then(res => res.json()))
: (url, params = {}) =>
U.fromPromise(() => {
const controller = new AbortController()
return {
ready: fetch(url, {...params, signal: controller.signal}).then(
res => res.json()
),
abort() {
controller.abort()
}
}
})
See the live GitHub search CodeSandbox for an example.
Note that U.fromPromise
is not the same as Kefir's
fromPromise
.
≡ ▶ U.sink(observable)
U.sink
creates a property that simply immediately produces undefined
and
subscribes to the given observable for as long as the returned sink is
subscribed to. See also U.consume
.
≡ ▶ U.skipIdenticals(observable)
U.skipIdenticals
is shorthand for
U.skipDuplicates(Object.is)
.
≡ ▶ U.skipWhen(predicate, observable)
U.skipWhen(predicate)
is shorthand for U.skipUnless(x =>
!predicate(x))
.
≡ ▶ U.startWith(value, observable)
U.startWith
creates a property that starts with the given value. It uses the
toProperty
method of Kefir.
≡ ▶ U.template([ ... ] | { ... })
U.template
composes an observable property from an arbitrarily nested template
of objects and arrays observables.
For example:
const abProperty = U.template({a: anObservable, b: anotherObservable})
abProperty instanceof Kefir.Observable // true
≡ ▶ Subscribing
≡ ▶ U.on({value, error, end}, observable)
U.on
subscribes to an observable and dispatches the events to the actions
specified in the template.
Note that explicitly subscribing to an observable should be done very rarely in a Karet application! Full Karet applications can be written with zero uses of explicit observable subscriptions, because the Reactive VDOM of Karet automatically subscribes to and unsubscribes from observables. Nevertheless, it can sometimes be convenient to subscribe explicitly to observables to perform side-effects.
For example:
U.thru(
observable,
...,
U.on({
value: value = console.log(value)
})
)
≡ ▶ Lifted standard functions
Standard JavaScript functions only operate on plain values. Karet Util provides lifted versions of some useful standard JavaScript functions. The below just directly links to relevant documentation in MDN.
≡ ▶ JSON
≡ ▶ URIs
U.decodeURI(string)
⌘U.decodeURIComponent(string)
⌘U.encodeURI(string)
⌘U.encodeURIComponent(string)
⌘
≡ ▶ Math
U.abs(x)
⌘U.acos(x)
⌘U.acosh(x)
⌘U.asin(x)
⌘U.asinh(x)
⌘U.atan(x)
⌘U.atanh(x)
⌘U.atan2(y, x)
⌘U.cbrt(x)
⌘U.ceil(x)
⌘U.clz32(x)
⌘U.cos(x)
⌘U.cosh(x)
⌘U.exp(x)
⌘U.expm1(x)
⌘U.floor(x)
⌘U.fround(x)
⌘U.hypot(...xs)
⌘U.imul(x, y)
⌘U.log(x)
⌘U.log1p(x)
⌘U.log10(x)
⌘U.log2(x)
⌘U.max(...xs)
⌘U.min(...xs)
⌘U.pow(x, y)
⌘U.round(x)
⌘U.sign(x)
⌘U.sin(x)
⌘U.sinh(x)
⌘U.sqrt(x)
⌘U.tan(x)
⌘U.tanh(x)
⌘U.trunc(x)
⌘