README
Realue
⚛️ Simple value management for React components.
Features
- Frees developers from value handling logic so that they can focus on the user experience.
- Enforces reusable components based on
{ value, name, onChange(value, name, payload?) }
properties. - Provides helpers for commonly used value types.
Contents
Installation
Install with the Node Package Manager:
npm install realue
Alternatively, checkout this repository and install the development dependencies to build the module file:
git clone https://github.com/davidbonnet/realue.git
cd realue
npm install
Import
Everything is exported from the main entry-point.
With JavaScript 6 modules:
import { object } from 'realue'
With CommonJS:
const { object } = require('realue')
API
Overview
Show caption
- 🏗 Under construction: the implementation is subject to change soon
- ➡️ Arguments
- ⬆️ Used props:
{ required, optional? }
- ⬇️ Injected props:
{ always, optional? }
The realue
module exposes the following functions:
- Element creator
- Value-based decorators
- Promised-based tools
- Tooling decorators
- Context
- Lifecycle
- Scoped-based decorators
- Property-based decorators
- Children-based decorators
- Type-oriented decorators
- DOM-based decorators
- Query helpers
- Immutability-oriented tools
- Asynchronous helpers
- Storage helpers
- Prop helpers
- Layout components
- Formatters
Element creator
$()
➡️
(component, propsOrChild, ...children)
Creates a react element from the provided component
, setting its props to propsOrChild
if it is an object or null
, and its children to propsOrChild
if it is not an object and the rest of the provided children
.
Similar to a reduced version of hyperscript.
Example
$(
'div',
$('h1', 'Realue'),
$('p', 'A simple set of tools and decorators for React.'),
$('p', { style: { color: 'gold' } }, 'Watchout, it is very addictive.'),
)
Value-based decorators
defaultValue
⬆️
{ defaultValue?, value? }
⬇️
{ value? }
Sets value
to defaultValue
if value
is nil
.
initialValue
⬆️
{ initialValue?, value? }
⬇️
{ value? }
Sets value
to initialValue
on first render, if initialValue
is not nil
, then to value
for subsequent renders.
resilient
⬆️
{ value? }
⬇️
{ value }
Keeps the last non-nil
value of prop value
.
transformable
⬆️
{ transformValue?, transformOnChange? }
⬇️
{ value?, onChange? }
Replaces value
with the return value of transformValue(value, previous?: { transformedValue, value })
, if set. Note that previous
is not provided when the component first mounts, since there are no previous prop values.
Replaces value
passed to onChange(value, name, payload)
with the return value of transformOnChange(value, name, payload, previous: { transformedValue?, value? })
, if set.
filterable
⬆️
{ filterValue?, filterOnChange? }
⬇️
{ onPush }
Prevents value
update if filterValue(value, previousValue)
is set and returns false
.
Prevents onChange
call if filterOnChange(value, name, payload)
is set and returns false
. Using onPush
calls onChange
unconditionally.
delayable
⬆️
{ onChange, delay }
⬇️
{ onChange, onPush(value, name, payload?) }
Delays onChange
calls until after delay
milliseconds have elapsed since the last call.
Renames undelayed onChange
as onPush
.
suspendable
⬆️
{ value, delay? }
⬇️
{ value? }
Suspends value
changes for delay
milliseconds. Subsequent value
or delay
changes cancel previous suspensions. Last suspension is canceled if value
is set to the value prior the start of the suspension.
Calling the injected method onPull
immediately sets value
to the latest value.
synced
⬆️
{ value?, onPull? }
⬇️
{ value, onChange, onPull? }
Enables prop value
to be locally editable while staying in sync with its parent value.
The prop can be updated with prop onChange(value, name, payload)
, which triggers the optional parent prop onChange
.
Calling onPull()
sets the local value to the parent value.
The return value of the optional parent prop onPull(newValue, previousValue)
is used on prop value
changes or when calling onPull()
.
editable
⬆️
{ value, name, onChange, onPull(value, previousValue)? }
⬇️
{ value, onChange, onPull(), onPush(payload?) }
Enables the value
prop to be locally editable when onChange
is set, while staying in sync with its parent value.
The value can be updated with prop onChange(value, name, payload)
, which triggers the parent prop onChange
.
Calling onPull()
sets the local value to the parent value.
The return value of the optional parent prop onPull(newValue, previousValue)
is used on value
changes or when calling onPull()
.
cyclable
⬆️
{ value, values?, name, onChange }
⬇️
{ onCycle(payload?) }
Injects prop cycle(payload)
that cycles the value
prop through the values of values
prop, which default to [false, true]
. Calls onChange(value, name, payload)
.
promised
⬆️
{ value? }
⬇️
{ value }
Replaces promise value
with { done, error, value }
.
Before the promise resolves, done
is false
and value
is undefined
.
If an error occured in the promise, error
is set to it. Otherwise, the value
is set to the resolved value and done
is true
.
If a new promise is provided, the previously resolved value
is kept until the new one resolves.
toggledEditing
⬆️
{ editing?, onChange, onPush? }
⬇️
{ editing, onChangeEditing, onToggleEditing }
Sets the editing
prop and enables its toggling through the onToggleEditing()
prop.
fromValue()
➡️
(path)
⬆️
{ name, onChange? }
⬇️
{ onChange(value) }
Adapts onChange
for components that call it by providing the value
as a first argument. If the path
is not nil
, extracts the value from get(value, path)
.
flattenValue
⬆️
{ value }
⬇️
{ ...value }
Merges the properties of the value
object prop into the props.
persisted
⬆️
{ value?, onChange? }
⬇️
{ value? }
Persists prop value
in the storage found in prop storage
, optionally prepending the value found in domain
to the key when looking for the value. On mount, if the value is found in the storage, it is set to prop value
. Its value is updated in the storage when onChange(value, name, payload)
is called.
Promised-based tools
on()
➡️
(target, event, listener?, options?)
Listens for event
on target
, calling listener(event)
at each incoming event
. The provided options
are identical to those provided to addEventListener
.
Returns a function that removes the listener
from the target
for the specified event
.
If listener
is not defined, returns a function that accepts the remaining (listener, options)
arguments.
sleep()
➡️
(duration, signal)
Returns a promise that resolves after at least duration
milliseconds.
If a signal
is provided, listens to it to cancel the promise.
until()
➡️
(register, signal, sentinel = stubTrue)
Listens for an event with the provided register
function until sentinel(event)
returns a truthy value.
If a signal
is provided, listens to it to cancel the promise.
untilOnline()
➡️
()
Returns a promise that waits for the browser to be back online.
Resolves to true
if it it was offline before calling this function, false
otherwise.
If a signal
is provided, listens to it to cancel the promise.
listenable()
➡️
(initialValue)
Returns a listenable value set to the provided initialValue
encapsulated in an object with the following properties:
value
: the actual valueon(listener)
: alistener
registerer that returns an unregisterer for this functionset(value)
: a newvalue
setter that gets emitted to all registered listeners
Example
const height = listenable(0)
function log(value) {
console.log(`Updated to ${value}`)
}
const off = height.on(log)
// Returns 0
height.value
// Calls `log(300)`
height.set(300)
// Stops loging value changes
off()
Tooling decorators
logProps()
➡️
(propNames, title?)
Logs the provided propNames
whenever they change.
Uses title
as console group (defaults to decorated component name).
This function can be made available globally by importing realue/src/register-loggers.js
first.
setDisplayName()
➡️
(name)
Sets the provided display name
to the component.
omitProps()
➡️
(propNames)
Removes provided propNames
.
groupProps()
➡️
(destinationName, propNames)
Groups propNames
into an object stored at destinationName
and updates them when any property value listed in propNames
changes.
Context
fromContext()
➡️
(provider, propName = 'value')
⬆️
{}
⬇️
{ [propName] }
Injects a context provider
that takes its value from [propName]
.
withContext()
➡️
(consumer, propName = 'value')
⬆️
{}
⬇️
{ [propName] }
Injects the value of the context consumer
into [propName]
.
Lifecycle
withEffect()
➡️
(shouldHandleOrKeys, handler)
Similar to useEffect
. Runs handler(props)
at mount and on update when shouldHandleOrKeys
, in case it is an array of prop names, mentions a prop name whose value changed, or, in case of a function, returns true
when called with (prevProps, nextProps)
.
If the handler returns a callback, it is called on update before the next handler
call or on unmount.
Example
// Listens for a given event and updates whenever `event` or `listener` changes
const withListener = withEffect(
['event', 'listener'],
({ event: eventName, listener }) => {
window.addEventListener(eventName, listener)
return () => window.removeEventListener(eventName, listener)
},
)
withImmediateEffect()
➡️
(shouldHandleOrKeys, handler)
Similar to withEffect
, except that it runs the handler
at component construction and before each render if shouldHandleOrKeys
returns true
.
withGlobalEffect()
➡️
(handler)
Runs handler()
when the first element of this component is mounted.
If the handler returns a callback, it is called when the last element of this component is unmounted.
If the handler returns false
, it will never be run again for this component.
withImmediateGlobalEffect()
➡️
(handler)
Runs handler()
when the first element of this component is constructed (that is, before it mounts).
If the handler returns a callback, it is called when the last element of this component is unmounted.
If the handler returns false
, it will never be run again for this component.
onPropsChange()
➡️
(shouldHandleOrKeys, handler, callOnMount = true)
Similar to withPropsOnChange
, except that the values of the handler
are not merged into the props.
The handler
is called when the component is first mounted if callOnMount
is true
(default value).
withHook()
➡️
(hook, source, result)
Uses the provided hook
, with the arguments extracted from source
,
and reinjects the value from result
back into the props.
Example
const Counter = compose(
withProps({ initialCount: 0 }),
withHook(useState, ['initialCount'], ['count', 'onChangeCount']),
)(({ count, onChangeCount }) =>
$(
'div',
'Count: ',
count,
$('button', { onClick: () => onChangeCount(count + 1) }, 'Increment'),
),
)
Scoped-based decorators
scoped()
➡️
(decorators)
⬆️
{ [any] }
⬇️
{}
Processes the decorators
in an isolated props scope so as to avoid poluting the passed props.
Example
compose(
withProps({ value: 1 }),
scoped(withProps({ value: 2, otherValue: 3 })),
// Logs unique prop `value` that equals `1`
logProps(),
)
returned()
➡️
(propsMapper)
⬆️
{ [any] }
⬇️
{ __return }
Enables the injection of props from an isolated scope. The propsMapperOrMap
can be a function that takes the current props and returns the props to inject, or a name list or map of prop names similar to the one provided to picked()
.
Examples
scoped(...decorators, returned(picked({ user: 'value' })))
scoped(...decorators, returned(omitted(['value'])))
box()
Boxes the execution of one or several decorators
with the picked inputMapperOrMap
and injects into the props the one picked by outputMapperOrMap
.
Example
box(['value', 'request'], compose(withEntityQuery, queried, flattenValue), [
'value',
'done',
'error',
])
Property-based decorators
defaultProp()
➡️
({ name, defaultName? } | name)
⬆️
{ [name]?, [defaultName]? }
⬇️
{ [name]? }
Sets [name]
to [defaultName]
if [name]
is nil
.
dynamicProp()
➡️
(name)
⬇️
{ [name]: true | false }
Injects a property [name]
that cycles between true
and false
at each render.
initialProp()
➡️
({ name, initialName? } | name)
⬆️
{ [name]?, [initialName]? }
⬇️
{ [name]? }
Sets [name]
to [initialName]
on first render if [initialName]
is not nil
, then to [name]
for subsequent renders.
suspendableProp()
➡️
({ name, delayName?, onPullName? } | name)
⬆️
{ [name], [delayName] }
⬇️
{ [name], [onPullName] }
Suspends [name]
changes for [delayName]
milliseconds. Subsequent [name]
changes cancel previous suspensions.
Calling the injected method [onPullName]
immediately sets [name]
to the latest value.
If [delayName]
is falsy, no suspension occurs, nor the injection of [onPullName]
.
resilientProp()
➡️
({ name, constantName } | name)
⬆️
{ [name]? }
⬇️
{ [name] }
Keeps the last non-nil
value of prop [name]
.
If constantName
is provided, keeps the last non-nil
value of prop [name]
only if prop [constantName]
did change.
If delayName
is provided, unconditionally updates the value of prop [name]
only if prop [delayName]
is truthy.
delayableProp()
➡️
({ name, delayName?, onPushName?, mode? } | name)
⬆️
{ [name], [delayName] }
⬇️
{ [name], [onPushName] }
Delays [name]
calls until after [delayName]
milliseconds have elapsed since the last call if options.mode
is 'debounce'
(default value), or calls [name]
at most once every [delayName]
milliseconds if options.mode
is 'throttle'
. The mode
can also be a function that returns a callback based from the ([name], [delayName])
arguments.
Renames undelayed [name]
as ['onPush' + name]
.
If [delayName]
is falsy, no delay occurs nor the injection of [onPushName]
.
editableProp()
➡️
({ name, onChangeName? } | name)
⬆️
{ [name]? }
⬇️
{ [onChangeName] }
Enables a value prop of a given name
to be locally editable.
The value can be updated with onChangeName
.
syncedProp()
➡️
({ name, onChangeName?, onPullName? } | name)
⬆️
{ [name]?, [onPullName]? }
⬇️
{ [name], [onChangeName], [onPullName]? }
Enables a prop with a given name
to be locally editable while staying in sync with its parent value.
The prop can be updated with prop [onChangeName](value, name, payload)
, which triggers the optional parent prop [onChangeName]
.
Calling [onPullName]()
sets the local value to the parent value.
The return value of the optional parent prop [onPullName](newValue, previousValue)
is used on prop [name]
changes or when calling [onPullName]()
.
cycledProp()
➡️
({ name, valuesName?, onCycleName?, onChangeName?, nameName? } | name)
⬆️
{ [name]? }
⬇️
{ [onChangeName] }
Injects prop [onCycleName](payload)
that cycles the value of prop [name]
through the values found in prop [valuesName]
which default to [false, true]
.
Calls [onChangeName](value, name, payload)
with name
taken from prop [nameName]
or name
.
promisedProp()
➡️
(name)
⬆️
{ [name]? }
⬇️
{ [name] }
Replaces the promise at prop [name]
with { done, error, value }
.
Before the promise resolves, done
is false
and value
is undefined
.
If an error occured in the promise, error
is set to it. Otherwise, the value
is set to the resolved value amd done
is true
.
If the propmise at prop [name]
changes, done
, error
, and value
are reset and any previous promise is discarded.
persistedProp()
➡️
({ name, onChangeName, domainName, storageName } | name)
⬆️
{ [name]?, [onChangeName]? }
⬇️
{ [name]? }
Persists prop [name]
in the storage found in prop [storageName]
, optionally prepending the value found in [domainName]
to the key when looking for the value. On mount, if the value is found in the storage, it is set to prop [name]
. Its value is updated in the storage when [onChangeName](value, name, payload)
is called.
Example
const STORAGE = new Map()
const PersistedInput = compose(
withProps({ domain: 'persisted', storage: STORAGE }),
persisted,
string,
fromEvent('target.value'),
domProps,
)('input')
delayableHandler()
⬆️
({ handlerName, sentinelName })
⬇️
{ [handlerName] }
Delays [handlerName]
calls until after [sentinelName]
is truthy.
Children-based decorators
withChildren()
➡️
(Component, childProps?, { valueName?, destinationName? })
⬆️
{ [valueName]?, item? }
⬇️
{ children }
Builds an array that maps every item from the [valueName]
prop ('value'
by default) with the result of <Component {...childProps(props)(itemValue, itemIndex)}
and injects it as a [destinationName]
prop ('children'
by default).
Example
function Item({ value }) {
return $('li', value)
}
const List = compose(array, withChildren(Item))('ul')
const element = $(List, { value: [1, 2, 3] })
withChild()
➡️
(Component | { [string]: [Component, childProps()] | Component }, childProps(props, name?)?, { destinationName? })
⬆️
{ [valueName]?, property? }
⬇️
{ children }
If ChildComponentOrMap
is a component, builds an element from the provided ChildComponentOrMap
with the props from childProps(props, undefined)
and injects it as a [destinationName]
prop ('children'
by default).
Otherwise, if ChildComponentOrMap
is a mapping of name: [Component, childProps()] | Component
, transforms this mapping into name: $(Component, childProps(props, name))
and injects it into the props at destinationName
('children'
by default).
If childProps
is not defined, defaults to returning the result of props.property(name)
merged into the props, if props.property
and props.name
are defined. Otherwise, all props
are provided.
Examples
const Person = compose(
withChild({
name: StringInput,
lastName: StringInput,
age: NumberInput,
}),
)(({ children }) => $('div', children.name, children.lastName, children.age))
const Article = compose(withChild(Toolbar))(({ value, children }) =>
$('div', $('p', value), children),
)
switchChild()
➡️
(propNameOrPicker, { [string]: [Component, childProps()] | Component }, { destinationName? })
⬆️
props | { [propNameOrPicker]? }
⬇️
{ children }
Builds the element from componentMap[key]
, with key
being the value of the prop name propNameOrPicker
, if propNameOrPicker
is a string, or of the value returned by propNameOrPicker(props)
, if propNameOrPicker
is a function.
The componentMap
values are either a [Component, childProps()]
couple or just a Component
.
Example
const EntityName = compose(
switchChild('type', {
user: UserName,
device: DeviceName,
setting: SettingName,
invoice: InvoiceName,
}),
)(Children)
const name = $(EntityName, { type: 'user', id: '42' })
Type-oriented decorators
object
⬆️
{ value?, name, onChange? }
⬇️
{ value?, property(name, key?), onChangeProperty(value, name, payload?) }
Provides property(name, key?)
that returns the props for the child element responsible of the property name
.
Also provides onChangeProperty(value, name, payload?)
that sets the property name
to the provided value
.
Sets value
to {}
if not set.
objectProp()
Provides [propertyName](name, key = name)
that returns the props for the child element responsible of the property name
of the object at [name]
.
If [onChangeName]
is set, also provides [onChangePropertyName](value, name, payload?)
that sets the property name
of the object at [name]
to the provided value
, and [onChangePropertiesName](values, payload?)
that merges the provided values
into the object at [name]
.
Sets [name]
to {}
if nil
.
➡️
(name | { name, onChangeName?, onChangePropertyName?, onChangePropertiesName?, propertyName?, nameName? })
⬆️
{ [name]?, [onChangeName]? }
⬇️
{ [name], [propertyName], [onChangePropertyName]?, [onChangePropertiesName]? }
splittable
⬆️
{ value?, name, onChange? }
⬇️
{ properties(names, key?), onChangeProperties(values, names, payload?) }
Enables dispatching a subset of properties to a child element.
array
⬆️
{ value?, name, onChange? }
⬇️
{ value?, item(index, key = index), onChangeItem(value, index, payload?), onAddItem(item, index, payload) }
Provides item(index, key = index)
that returns the props for the child element responsible of the item index
.
Also provides onChangeItem(value, index, payload?)
that sets the item index
to the provided value
, and onAddItem(value, index, payload?)
that inserts an item with the provided value
at index
.
Sets value
to []
if not set.
removable
⬆️
{ name, onChange? }
⬇️
{ onRemove(payload?) }
Provides onRemove(payload?)
, which sets the value to undefined
and results in removing the item or property.
boolean
⬆️
{ value? }
⬇️
{ value? }
Sets value
to false
if not set.
string
⬆️
{ value? }
⬇️
{ value? }
Sets value
to ''
if not set.
🏗 number
⬆️
{ value? }
⬇️
{ value? }
Sets value
to 0
if not set.
🏗 date
⬆️
{ value? }
⬇️
{ value? }
Sets value
to new Date(0)
if not set.
DOM-based decorators
fromEvent()
➡️
(path)
⬆️
{ name, onChange? }
⬇️
{ onChange(event) }
Creates an onChange
handler that takes the value from get(event, path)
.
If path
is nil
, the value is taken from the value
prop instead.
syncedFocus
⬆️
{ focus }
⬇️
{ onFocus, onBlur }
Exposes the synced focus
state of an element through the onFocus()
and onBlur()
callbacks.
refreshable
⬇️
{ onRefresh() }
Adds an onRefresh()
prop that enables refreshing the component.
refreshed
⬆️
{ delay? }
⬇️
{ onRefresh }
Refreshes the component at a given delay
interval. See interval
for the behavior based on delay
.
onKeysDown()
➡️
(keys)
⬆️
{}
⬇️
{ onKeyDown }
Triggers the specified keys
handlers on key down. Each handler is called with the current (props, event)
.
domProps
⬆️
{...domProps, ...nonDomProps}
⬇️
{...domProps}
Only keeps DOM properties.
withNode
⬆️
{}
⬇️
{ node }
Injects a node
reference created with React.createRef()
to be applied on any element through the ref
attribute.
Example
const Example = withNode(({ node }) =>
$('div', { ref: node }, node.current ? 'Referenced' : 'Not referenced'),
)
forwardNode
⬆️
{ key }
⬇️
{ node }
Renames the provided ref
into node
.
withBounds()
➡️
(properties = ['height', 'width', 'top', 'left'], offset?)
⬆️
{ node, delay? }
⬇️
{ top?, left?, width?, height?, pullBounds }
Injects bounds properties
returned from offset(node.current)
, and updateBounds
which triggers a bounds update. Uses the optional delay
prop as a debounce duration when reading element bounds.
Example
withBounds(['width', 'height'])(({ width, height }) =>
$('div', 'Dimensions: ', width, ' x ', height),
)
Query helpers
object Query
Either a single query continaing the following properties:
type: string
: a string identifying the type object to fetchmethod: enum { 'get', 'list', 'post', 'put', 'patch', 'delete' }
: method to apply on the queried objectrefresh: boolean
: iftrue
, bypasses any cachevalue
: details of the object to fetch in case ofget
, or contents to save in case ofpost
,put
, andpatch
fields: string[]
: array of the field names of the object to fetchstart: number
: in case oflist
method, start offsetlimit: number
: in case oflist
method, maximum amount of items to returnfilter: { [string]: string | boolean | number | object }
: values used to filterorder: { key: string, descending: boolean }[]
: array of ordering parameters
Or multiple queries grouped into a single property:
queries: query[] | { [string]: query }
: array or map of queries
queriedProp()
➡️
({ queryName, valueName?, onAbortName?, requestName? } | name)
⬆️
{ [queryName], [requestName = 'request'] }
⬇️
{ [valueName?], [onAbortName?] }
Calls [requestName](query)
whenever the query at [queryName]
changes and stores the result progress at [valueName]
.
An abortion method at [onAbortName]
is injected. If called before the query resolves, it aborts it, sending the exception to [valueName].error
.
queried
⬆️
{ query, request }
⬇️
{ value, onAbort }
Calls request(query)
whenever the query at query
changes and stores the result progress at value
.
An abortion method at onAbort
is injected. If called before the query resolves, it aborts it, sending the exception to value.error
.
retry()
➡️
({ amount?, delay?, delayDelta? })
Retries a failed query call up to amount
times, with a given delay
in milliseconds at ±delayDelta
milliseconds.
Note that an amount
set to Infinity
results in indefinitely trying to resolve a query call.
Only instances of QueryError
will result in new tries. Other errors will propagate immediately.
split()
➡️
(condition, left, right?)
Dispatches an incoming query to left
if condition(query)
returns a truthy value, right
otherwise. This is helpful for sending queries to different resolvers.
Example:
const request = compose( split(query => query.protocol === 'gql', gqlHandlers), fetchJson(), )(identity)
cache()
➡️
({ serialize?, engine?, duration? })
Caches the result of a query if serialize
returns a non-empty string key. The engine
should follow the Map
API. Elements are kept in the cache until the duration
in milliseconds expires.
Note that a duration
set to Infinity
indefinitely keeps items in the cache.
aggregate()
➡️
({ categorize?, serialize?, delay?, reduce?, pick? })
Aggregates multiple incoming query calls into one query.
Queries are grouped according to the string key returned by categorize(query)
. Inside a group, each query is identified with serialize(query)
.
The aggregated query is built from the object returned by reduce(queries)
, after at least delay
milliseconds after the first non-aggregated aggregatable query call.
When the aggregated query resolves, the result is dispatched back to each aggregatable query call of the category by dispatching the result for each query returned by pick(result, query)
.`
concurrent
Runs concurrent queries if query.queries
contains a list or a map of queries, resulting in a list or map of resolved queries.
Otherwise, passes the query to the next handler.
toFetchQuery(routes, transform?)
➡️
(routes, transform?)
Converts a query
into a DOM Fetch query. The resulting query
is passed onto transform(query)
before sending it.
To be used in conjunction with fetchJson()
.
fetchJson()
Calls the DOM Fetch query
and processes the successful response with the provided responseHandler
, which defaults to requesting the parsed json()
response.
To be used in conjunction with toFetchQuery()
.
logQuery()
➡️
(title?)
Logs the outgoing query and the incoming result or the error.
This function can be made available globally by importing realue/src/register-loggers.js
first.
queryString()
➡️
(values)
Returns a key-sorted query string from provided values
object.
searchParams()
➡️
(query)
Returns an object containing all search parameters of a provided query
.
Immutability-oriented tools
EMPTY_ARRAY
Immutable empty array. Using this instead of []
avoids having several instances of immutable empty arrays.
EMPTY_OBJECT
Empty object to be used in immutable values. Using this instead of {}
avoids having several instances of immutable empty objects.
insertItem()
➡️
(array, value, index)
Returns a new array with the value
inserted into the array
at the provided index
, provided value
is not undefined
, in which case the array
is returned untouched.
If the index
is not provided, the value
appended to the array
.
If the array
is nil
, it is considered as an EMPTY_ARRAY
.
replaceItem()
➡️
(array, previousValue, value)
Returns a new array with the first occurence of the previousValue
in array
replaced by value
.
Returns the same array
if the previousValue
is not found.
If the array
is nil
, it is considered as an EMPTY_ARRAY
.
setItem()
➡️
(array, index, value)
Returns a new array with array[index]
set to value
if array[index]
is strictly different from value
. Otherwise, returns the provided array
.
If value
is undefined
, ensures that the returned array does not contain the item found at index
.
If index
is greater than array.length
, appends value
to the array
.
If index
equals -1
or is undefined
, returns the array
untouched.
If the array
is nil
, it is considered as an EMPTY_ARRAY
.
setProperty()
➡️
(object, key, value)
Returns a new object with object[key]
set to value
if object[key]
is strictly different from value
. Otherwise, returns the provided object
.
If value
is undefined
, ensures that the returned object does not contain the key
.
If key
is undefined
, returns the object
untouched.
If object
is nil
, it is considered as an EMPTY_OBJECT
.
setProperties()
➡️
(object, values)
Returns a new object with the properties of values
merged into object
.
setPath()
➡️
(target, path, value)
Returns a new object or array based on target
with its path
set to value
.
Recursively uses setItem
and setProperty
based on the type of each path
item (number
and object
, respectively).
If path
is nil
, returns value
.
Asynchronous helpers
timeout()
➡️
(duration, callback)
Calls callback
after at least duration
milliseconds. Returns a function that cancels the future call of callback
, if not already called.
interval()
➡️
(duration, callback)
Calls callback
at least every duration
milliseconds. Returns a function that stops future calls of callback
. If duration
is falsy, uses requestAnimationFrame
.
Storage helpers
sessionStorage
Storage that persists between page reloads, until the tab or window is closed. To be used with persistedProp()
or persisted
.
localStorage
Persistent storage. To be used with persistedProp()
or persisted
.
Prop helpers
getter()
➡️
(path, defaultValue)
Returns a getter function with the provided path
and defaultValue
.
picked()
➡️
(propNamesOrMap)
Returns a function that returns a subset of the provided object or a mapping of selected property paths.
Examples
// Only keeps the `value` prop
mapProps(picked(['value']))
// Only keeps the `value` prop renamed to `user`
mapProps(picked({ user: 'value' }))
// Injects selected properties of `value`
withProps(
picked({ done: 'value.done', error: 'value.error', value: 'value.value' }),
)
omitted()
➡️
(propNames)
Returns a function that returns all props without the ones whose name is in propNames
.
Example
// Only omit the `value` prop
returned(omitted(['value']))
hasProp()
➡️
(name)
Returns a function that checks if props[name]
is not nil
.
hasNotProp()
➡️
(name)
Returns a function that checks if props[name]
is nil
.
hasProps()
➡️
(names)
Returns a function that checks if every prop name
in names
is not nil
.
hasNotProps()
➡️
(names)
Returns a function that checks if some prop name
in names
is nil
.
same()
➡️
(a, b, properties, deep = false)
Returns true
if objects a
and b
have the same properties
.
Unless provided, properties
are the combined set of property names from a
and b
.
If deep
is true
, considers properties as paths (e.g., p1.p2
).
different()
➡️
(properties, deep = true)
Returns a function that returns true
if one of the properties
of the objects (a, b)
differs. This is usefull when deep-nested comparisons are required.
Example
// Extracts the name from a `value` prop and updates it only if it changes
const withName = withPropsOnChange(
different(['value.name']),
({ value: { name } }) => ({ name }),
)
Layout components
:warning: These components must be imported from 'realue/layout'
.
Flex
Abstracts usage of CSS flexbox.
$(
Flex,
{ container: true, direction: 'column' },
$(Flex, { item: true }, 'Small'),
$(Flex, { item: true, grow: true }, 'Large'),
$(Flex, { item: true }, 'Small'),
)
Box
Merges provided style properties into style
property.
$(Box, { width: 100, height: '50%', backgroundColor: 'gold' })
Formatters
escapeRegex()
➡️
(pattern)
Escapes special characters of a given regular expresion pattern
.
replaceAll()
➡️
(string, find, replace)
Replaces all occurrences of find
by replace
in the provided string
.
Demo
A demo application can be run in the browser with:
npm run build:watch
open http://localhost:1234
You can then inspect and edit the code in the demo/
folder.