Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/ryanmorr/reflex
Reactive DOM
https://github.com/ryanmorr/reflex
dom javascript reactive ui
Last synced: 23 days ago
JSON representation
Reactive DOM
- Host: GitHub
- URL: https://github.com/ryanmorr/reflex
- Owner: ryanmorr
- License: unlicense
- Created: 2020-08-31T16:46:21.000Z (over 4 years ago)
- Default Branch: master
- Last Pushed: 2023-05-15T18:27:30.000Z (over 1 year ago)
- Last Synced: 2024-11-13T20:41:06.976Z (3 months ago)
- Topics: dom, javascript, reactive, ui
- Language: JavaScript
- Homepage:
- Size: 369 KB
- Stars: 2
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# reflex
[![Version Badge][version-image]][project-url]
[![License][license-image]][license-url]
[![Build Status][build-image]][build-url]> Reactive DOM
## Install
Download the [CJS](https://github.com/ryanmorr/reflex/raw/master/dist/cjs/reflex.js), [ESM](https://github.com/ryanmorr/reflex/raw/master/dist/esm/reflex.js), [UMD](https://github.com/ryanmorr/reflex/raw/master/dist/umd/reflex.js) versions or install via NPM:
```sh
npm install @ryanmorr/reflex
```## Usage
Reflex is a small, but versatile UI library that combines declarative DOM building with reactive stores that bind data to DOM nodes, automatically keeping the DOM in sync when the data is changed:
```javascript
import { html, store } from '@ryanmorr/reflex';const count = store(0);
const element = html`
Count: ${count}
count.update((val) => val + 1)}>Increment
`;document.body.appendChild(element);
```## API
### `store(value?)`
Create a reactive store that encapsulates a value and can notify subscribers when the value changes:
```javascript
import { store } from '@ryanmorr/reflex';// Create a store with an initial value
const count = store(0);// Get the store value
count.value(); //=> 0// Set the store value
count.set(1);// Set the store value with a callback function
count.update((val) => val + 1);// Subscribe a callback function to be invoked when the value changes,
// it returns a function to unsubscribe from future updates
const unsubscribe = count.subscribe((nextVal, prevVal) => {
// Do something
});
```------
### `derived(...stores, callback)`
Create a reactive store that is based on the value of one or more other stores:
```javascript
import { derived, store } from '@ryanmorr/reflex';const firstName = store('John');
const lastName = store('Doe');
const fullName = derived(firstName, lastName, (first, last) => `${first} ${last}`);fullName.value(); //=> "John Doe"
firstName.set('Jane');
fullName.value(); //=> "Jane Doe"
// Subscribe to be notified of changes
const unsubscribe = fullName.subscribe((nextVal, prevVal) => {
// Do something
});
```If the callback function defines an extra parameter in its signature, the derived store is treated as asynchronous. The callback function is provided a setter for the store's value and no longer relies on the return value:
```javascript
import { derived, store } from '@ryanmorr/reflex';const query = store();
// Perform an ajax request when the query changes
// and notify subscribers with the results
const results = derived(query, (string, set) => {
fetch(`path/to/server/${encodeURIComponent(string)}`).then(set);
});
```------
### `html(strings, ...values?)`
Create DOM nodes declaratively via tagged template literals:
```javascript
import { html } from '@ryanmorr/reflex';// Create an element
`;
const el = html`// Create a text node
const text = html`Hello World`;// Create an SVG element
const rect = html``;// Create a document fragment for multiple root nodes
`;
const frag = html`// Supports attributes
`;
const div = html`// Supports spread attributes
const section = html``;// Supports styles as an object
const header = html``;// Supports styles as a string
const em = html``;// Supports functions for setting child nodes
const header = html`${(parentElement) => html`Title
`}`;// Supports functions for setting attributes (except event listeners)
const footer = html` 'foo'}>`;// Supports event listeners (indicated by a prefix of "on")
const button = html` console.log('clicked!')}>Click Me`;
```#### Bindings
When a reactive store is interpolated into a DOM element created with `html`, it creates a reactive binding that will automatically update that portion of the DOM, and only that portion, when the internal store value changes:
```javascript
import { html, store } from '@ryanmorr/reflex';const name = store('John');
// Interpolate a store into an element
const element = html`My name is ${name}`;// The store value is appended as a text node
element.textContent; //=> "My name is John"// The store is bound to the text node, changing
// the store value automatically updates the text
// node and only that text node
name.set('Jim');// After rendering is completed
element.textContent; //=> "My name is Jim"
```Similarly to stores, promises can also be interpolated into a DOM element created with `html`, setting the value of the node/attribute when the promise resolves:
```javascript
import { html } from '@ryanmorr/reflex';const promise = Promise.resolve('World');
// Interpolate a promise like anything else
const element = html`Hello ${promise}`;// After the promise resolves and rendering is completed
element.textContent; //=> "Hello World"
```#### Components
Functional components are also supported. Since reflex is not virtual DOM, a component is only executed once, making both stateless and stateful components easy:
```javascript
import { html, store } from '@ryanmorr/reflex';// A simple component to wrap a common pattern with props and child nodes
const Stateless = ({id, children}) => {
return html`${children}`;
};// Create the component and return a DOM element
const section = html`<${Stateless} id="foo">bar/>`;// A component that holds state
const Stateful = () => {
const getTime = () => new Date().toLocaleTimeString();
const time = store(getTime());
setInterval(() => time.set(getTime()), 1000);
return html`Time: ${time}`;
};// Create the stateful component just like a stateless one
const div = html`<${Stateful} />`;
```If the component function defines an extra parameter as part of its signature, it is provided a function for registering callbacks to be invoked when the component is mounted to the DOM. Optionally, the mount callback can return a cleanup function that is executed when the component is disposed:
```javascript
import { html } from '@ryanmorr/reflex';const Component = (props, mount) => {
mount((element) => {
// Executed when the component is appended to
// the DOM and is provided the root element(s)return () => {
// Executed when the component is disposed
};
});return html`
`;
};
```#### Refs
When creating elements with `html`, the `ref` attribute can be used to invoke a function when the element is first created. This is useful for initializing elements and collecting references to deeply nested elements:
```javascript
import { html } from '@ryanmorr/reflex';const element = html`
/* initialize element */}>`;
```Additionally, assigning a store as the value of a `ref` attribute will add the element to an internal array within the store. Subscribers of the store will be notified when elements are added and removed:
```javascript
import { html, store, dispose } from '@ryanmorr/reflex';// Use a store to group multiple element references
const foo = store();
const element = html`
`;
// Returns an array of all elements in the store
const elements = foo.value();
// Subscribe to be called when elements are added or removed
foo.subscribe((nextElements, prevElements) => {
// Do something
});
// Disposing an element will automatically remove it from the store
dispose(element.lastChild);
```
------
### `effect(...stores?, callback)`
Create a side effect that is executed every time the DOM has been updated and return a function to stop future calls:
```javascript
import { effect } from '@ryanmorr/reflex';
const stop = effect(() => {
// DOM has been updated
});
```
Providing one or more dependencies will create a side effect that is guaranteed to execute after a store value changes and any portion of the DOM that depends on that store has been updated:
```javascript
import { effect, store } from '@ryanmorr/reflex';
const id = store('foo');
const content = store('bar');
const stop = effect(id, content, (idVal, contentVal) => {
// Invoked anytime `id` or `content` changes and the DOM has been updated
});
```
------
### `bind(store)`
Create a two-way binding between a store and a form field, allowing the store to be automatically updated with the current value of the form element when the user changes it, and vice-versa. It supports inputs, checkboxes, radio buttons, selects, and textareas:
```javascript
import { bind, html, store } from '@ryanmorr/reflex';
const value = store('foo');
const element = html``;
```
Alternatively, `bind` can be used to support stores as event listeners:
```javascript
import { bind, html, store } from '@ryanmorr/reflex';
const clicked = store();
const button = html`Click Me`;
clicked.subscribe((event) => console.log('clicked'));
```
------
### `each(store, callback, fallback?)`
Efficiently diffs and renders lists when provided a reactive store that encapsulates an iterable value. Upon reconciliation, the `each` function uses a strict equality operator (`===`) to compare the indexed values of the iterable and determine if an element has been removed or relocated:
```javascript
import { each, html, store } from '@ryanmorr/reflex';
const items = store([1, 2, 3, 4, 5]);
const element = html`
- ${index + 1}: ${item} `)}
${each(items, (item, index, array) => html`
`;
```
Provide a fallback function as an optional third argument to render content when the store contains an empty iterable or non-iterable value:
```javascript
import { each, html, store } from '@ryanmorr/reflex';
const items = store([]);
const element = html`
${each(items,
(item) => html`
() => html`
)}
`;
```
------
### `tick()`
Reflex uses deferred rendering to batch DOM updates. The `tick` function returns a promise that is resolved when all previously queued DOM updates have been rendered:
```javascript
import { tick } from '@ryanmorr/reflex';
// Embed a store in the DOM
const store = store('foo');
const element = html`
// Change a store value to trigger a re-render
store.set('bar');
// The DOM is up-to-date when the `tick` promise resolves
await tick();
```
------
### `cleanup(element, callback)`
Register a callback function to be invoked when an element is disposed. An element is disposed implicitly only during an `each` DOM reconciliation or explicitly when the `dispose` function is called on the element or an ancestor element:
```javascript
import { cleanup } from '@ryanmorr/reflex';
cleanup(element, () => console.log('element and child nodes disposed'));
```
------
### `dispose(element)`
Destroy all node-store bindings to prevent future DOM updates and invoke any registered `cleanup` functions for an element and its descendants. It will also remove the element and its descendants from any store it was added to via the `ref` attribute:
```javascript
import { dispose, store, html, tick } from '@ryanmorr/reflex';
// Create an element-store binding
const foo = store('foo');
const element = html`
// Update element
foo.set('bar');
await tick();
console.log(element.textContent); //=> "bar"
// Destroy the element-store binding
dispose(element);
// The element is no longer updated
foo.set('baz');
await tick();
console.log(element.textContent); //=> "bar"
```
## CSS
For a CSS-in-JS solution, refer to [fusion](https://github.com/ryanmorr/fusion), a similar library that brings reactivity to CSS variables, media queries, keyframes, and element queries among other helpers. It is also 100% compatible with reflex.
## License
This project is dedicated to the public domain as described by the [Unlicense](http://unlicense.org/).
[project-url]: https://github.com/ryanmorr/reflex
[version-image]: https://img.shields.io/github/package-json/v/ryanmorr/reflex?color=blue&style=flat-square
[build-url]: https://github.com/ryanmorr/reflex/actions
[build-image]: https://img.shields.io/github/actions/workflow/status/ryanmorr/reflex/node.js.yml?style=flat-square
[license-image]: https://img.shields.io/github/license/ryanmorr/reflex?color=blue&style=flat-square
[license-url]: UNLICENSE