Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/gamtiq/listate
Library for listening on changes of Redux store state.
https://github.com/gamtiq/listate
handler listen listener observe observer redux side-effect state store subscribe subscriber subscription watch watcher
Last synced: about 8 hours ago
JSON representation
Library for listening on changes of Redux store state.
- Host: GitHub
- URL: https://github.com/gamtiq/listate
- Owner: gamtiq
- Created: 2017-05-14T21:22:18.000Z (over 7 years ago)
- Default Branch: master
- Last Pushed: 2021-03-30T20:31:07.000Z (over 3 years ago)
- Last Synced: 2024-04-26T08:01:41.336Z (7 months ago)
- Topics: handler, listen, listener, observe, observer, redux, side-effect, state, store, subscribe, subscriber, subscription, watch, watcher
- Language: JavaScript
- Size: 629 KB
- Stars: 6
- Watchers: 3
- Forks: 1
- Open Issues: 5
-
Metadata Files:
- Readme: README.md
- Changelog: History.md
Awesome Lists containing this project
README
Library to listen/observe/watch changes of Redux store state.
[![NPM version](https://badge.fury.io/js/listate.png)](http://badge.fury.io/js/listate)
[![Build Status](https://secure.travis-ci.org/gamtiq/listate.png?branch=master)](http://travis-ci.org/gamtiq/listate)## Table of contents
* [Installation](#install)
* [Usage](#usage)
* [Examples](#examples)
* [API](#api)
* [Contributing](#contributing)
* [License](#license)### Node
npm install listate
### [Bower](http://bower.io)
bower install listate
### AMD, <script>
Use `dist/listate.js` or `dist/listate.min.js` (minified version).
Use `dist/extra.js` or `dist/extra.min.js` (minified version) to apply extra functions.### ECMAScript 6
```js
import listen from 'listate';
// Or if you need extra functionality
import extListen from 'listate/extra';
```### Node
```js
const listen = require('listate').listen;
// Or if you need extra functionality
const extListen = require('listate/extra').listen;
```### AMD
```js
define(['path/to/dist/listate.js', 'path/to/dist/extra.js'], function(listate, extra) {
const listen = listate.listen;
// Import extra.js if you need extra functionality
const extListen = extra.listen;
});
```### Bower, <script>
```html
// listate is available via listate field of window object
const listen = listate.listen;
// Extra functionality is available inside extra namespace
const extListen = listate.extra.listen;```
```js
import { createStore } from 'redux';
import listen from 'listate';
import extListen from 'listate/extra';const initState = {
user: null,
section: '',
map: {
main: {}
}
};function reducer(state, action) {
const { payload } = action;
let newState;
switch (action.type) {
case 'AUTH':
return Object.assign({}, state, {user: payload});
case 'SELECT_SECTION':
return Object.assign({}, state, {section: payload});
case 'SET_SECTION':
newState = Object.assign({}, state);
newState.map = Object.assign({}, state.map);
newState.map[payload.key] = payload.value;
return newState;
default:
return state;
}
}const store = createStore(reducer, initState);
listen(store, {
data: 'main',
filter: (state) => state.user,
// One-time listener
once: true,
handle(data) {
// Dispatch any action
data.dispatch({
type: 'SELECT_SECTION',
// data.current === state.user, data.data === 'main'
payload: data.current.favoriteSection || localStorage.getItem('selectedSection') || data.data
});
}
});
listen(store, {
filter: (state) => state.section,
when: (current, prev) => current !== prev && current !== 'exit',
// Call the listener no more frequently than once per second
delay: 1000,
handle(data) {
// data.current === state.section
localStorage.setItem('selectedSection', data.current);
console.log('Saved section: ', data.current);
}
});
listen(store, {
description: 'map change listener',
context: true,
filter: (state) => state.map,
when: (current, prev, data) => current.stat && data.state.user && data.state.section === 'video',
handle(data) {
console.log('data.prev:', data.prev); // {main: {}}
console.log('data.current:', data.current); // {main: {}, stat: {a: 1}}
console.log('this.description:', this.description); // map change listener
}
});
extListen(store, {
filter: {s: 'section', main: 'map.main'},
handle(data) {
console.log('extListen: data.prev -', data.prev);
console.log('extListen: data.current -', data.current);
}
});
...
store.dispatch({
type: 'AUTH',
payload: {login: 'commander'}
});
...
store.dispatch({
type: 'SELECT_SECTION',
payload: 'video'
});
...
store.dispatch({
type: 'SET_SECTION',
payload: {
key: 'stat',
value: {
a: 1
}
}
});
...
store.dispatch({
type: 'SELECT_SECTION',
payload: 'news'
});
...
store.dispatch({
type: 'SET_SECTION',
payload: {
key: 'main',
value: {
content: 'text'
}
}
});
```### Base functionality (listate, dist/listate.js)
#### baseWhen(state, prevState): boolean
Checks whether current value (state) is not equal previous value (state).
Returns value of the following comparison: `state !== prevState`.
#### listen(store, listener): Function
Adds/registers state change listener for the given store.
Arguments:
* `store: object` - Store for which listener should be added/registered.
* `listener: Function | object` - Specifies listener that should be called on a state change.
Can be a function or an object that defines listener settings/details.
* `listener.handle: Function` - Listener that should be called on a state change.
* `listener.context: boolean | object` (optional) - Specifies object that should be used as `this` value when calling the listener.
* `listener.data: any` (optional) - Any data that should be passed into the listener.
* `listener.delay: number` (optional) - Specifies that listener should be called after the given number of milliseconds
have elapsed. Works similar to `debounce`: when several requests for the listener call arrive during the specified period
only the last one will be applied after the timeout. `0` means that the listener should be called asynchronuosly.
* `listener.filter: (state) => state.part` (optional) - Function (selector) to extract state part
which will be used inside `when` to determine whether the listener should be called.
By default the entire state will be used.
* `listener.once: boolean` (optional) - Whether the listener should be called just once (by default `false`).
* `listener.when: (current, prev, data) => boolean` (optional) - Function to determine
whether the listener should be called. By default `baseWhen` is used.
The listener will be called if the function returns true.
The following parameters will be passed into the function:- The current state or a part of the current state if `filter` is set.
- The previous state or a part of the previous state if `filter` is set.
- An object that will be passed into listener.Returns a function that removes/unsubscribes the listener.
An object with the following fileds will be passed as parameter into the listener:
* `current: any` - The current state or a part of the current state if `filter` is set.
* `prev: any` - The previous state or a part of the previous state if `filter` is set.
* `state: object` - The current state.
* `prevState: object` - The previous state.
* `data: any` - The auxiliary data (value of `listener.data` parameter).
* `store: object` - The store for which listener is registered.
* `dispatch: Function` - Reference to `dispatch` method of the store.
* `unlisten: Function` - The function that removes/unsubscribes the listener.### Extra functionality (listate/extra, dist/extra.js)
#### getPathValue(obj, path): any
Return value of specified field path inside given object.
```js
import { getPathValue } from 'listate/extra';
const obj = {
a: {
b: {
c: 'value'
},
d: true
},
e: 4,
f: [1, 'z', null]
};
getPathValue(obj, 'a.b.c'); // 'value'
getPathValue(obj, 'a.c'); // undefined
```#### getObjectPart(source, parts): object
Create an object containing specified parts of the given object.
```js
import { getObjectPart } from 'listate/extra';
const obj = {
a: {
b: {
c: 'value',
d: true
},
e: 4,
f: [1, 'z', null]
},
g: 7,
h: {
i: false,
j: 0
},
k: 'king',
l: 'last'
};
getObjectPart(obj, {f1: 'a.b.d', f2: 'a.f.1', f3: 'g', f4: 'h.j'}); // {f1: true, f2: 'z', f3: 7, f4: 0}
```#### getFieldFilter(path): Function
Return a function that extracts value of the specified field path inside a given object.
```js
import { getFieldFilter } from 'listate/extra';
const filter = getFieldFilter('a.d');
const obj = {
a: {
b: {
c: 'value'
},
d: 17
},
e: 4,
f: [1, 'z', null]
};
filter(obj); // 17
```#### getPartFilter(parts): Function
Return a function that creates an object containing the specified parts of a given object.
```js
import { getPartFilter } from 'listate/extra';
const filter = getPartFilter({f1: 'a.b.c', f2: 'h.j', f3: 'k'});
const obj = {
a: {
b: {
c: 'value',
d: true
},
e: 4,
f: [1, 'z', null]
},
g: 7,
h: {
i: false,
j: 0
},
k: 'king',
l: 'last'
};
filter(obj); // {f1: 'value', f2: 0, f3: 'king'}
```#### unlike(state, prevState, deep): boolean
Check whether current object (state) is not equal previous object (state) comparing values of their fields.
```js
import { unlike } from 'listate/extra';
unlike({a: 1, b: 2}, {a: 1, b: 2}); // false
unlike({a: 1, b: {c: 3}}, {a: 1, b: {c: 3}}); // true
unlike({a: 1, b: {c: 3}}, {a: 1, b: {c: 3}}, true); // false
```#### unlikeDeep(state, prevState): boolean
Check whether current object (state) is not equal previous object (state) deeply comparing values of their fields.
The same as `unlike(state, prevState, true)`.
#### listen(store, listener): Function
Add/register state change listener for the given store.
It is a wrap around base `listate.listen` that supports the following enhanced listener settings:
* `listener.filter`.
When an array or an object is passed, the used filter will be result of `getPartFilter(listener.filter)`.
When a string is passed, the used filter will be result of `getFieldFilter(listener.filter)`.
* `listener.when`. By default `unlike` is used.See `doc` folder for details.
## Contributing [↑](#start)
In lieu of a formal styleguide, take care to maintain the existing coding style.
Add unit tests for any new or changed functionality.
Lint and test your code.## License [↑](#start)
Copyright (c) 2017-2019 Denis Sikuler
Licensed under the MIT license.