{"id":19009174,"url":"https://github.com/gamtiq/listate","last_synced_at":"2025-07-13T01:07:17.238Z","repository":{"id":53421637,"uuid":"91272239","full_name":"gamtiq/listate","owner":"gamtiq","description":"Library for listening on changes of Redux store state.","archived":false,"fork":false,"pushed_at":"2021-03-30T20:31:07.000Z","size":644,"stargazers_count":6,"open_issues_count":5,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-15T07:52:22.949Z","etag":null,"topics":["handler","listen","listener","observe","observer","redux","side-effect","state","store","subscribe","subscriber","subscription","watch","watcher"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gamtiq.png","metadata":{"files":{"readme":"README.md","changelog":"History.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-05-14T21:22:18.000Z","updated_at":"2019-07-11T14:17:53.000Z","dependencies_parsed_at":"2022-08-27T15:20:53.600Z","dependency_job_id":null,"html_url":"https://github.com/gamtiq/listate","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gamtiq%2Flistate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gamtiq%2Flistate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gamtiq%2Flistate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gamtiq%2Flistate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gamtiq","download_url":"https://codeload.github.com/gamtiq/listate/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250337909,"owners_count":21414102,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["handler","listen","listener","observe","observer","redux","side-effect","state","store","subscribe","subscriber","subscription","watch","watcher"],"created_at":"2024-11-08T19:06:50.604Z","updated_at":"2025-04-22T22:47:00.999Z","avatar_url":"https://github.com/gamtiq.png","language":"JavaScript","funding_links":[],"categories":["Marks"],"sub_categories":["[React - A JavaScript library for building user interfaces](http://facebook.github.io/react)"],"readme":"# listate \u003ca name=\"start\"\u003e\u003c/a\u003e\n\nLibrary to listen/observe/watch changes of Redux store state.\n\n[![NPM version](https://badge.fury.io/js/listate.png)](http://badge.fury.io/js/listate)\n[![Build Status](https://secure.travis-ci.org/gamtiq/listate.png?branch=master)](http://travis-ci.org/gamtiq/listate)\n\n## Table of contents\n\n* [Installation](#install)\n* [Usage](#usage)\n* [Examples](#examples)\n* [API](#api)\n* [Contributing](#contributing)\n* [License](#license)\n\n## Installation \u003ca name=\"install\"\u003e\u003c/a\u003e [\u0026#x2191;](#start)\n\n### Node\n\n    npm install listate\n\n### [Bower](http://bower.io)\n\n    bower install listate\n\n### AMD, \u0026lt;script\u0026gt;\n\nUse `dist/listate.js` or `dist/listate.min.js` (minified version).\nUse `dist/extra.js` or `dist/extra.min.js` (minified version) to apply extra functions.\n\n## Usage \u003ca name=\"usage\"\u003e\u003c/a\u003e [\u0026#x2191;](#start)\n\n### ECMAScript 6\n\n```js\nimport listen from 'listate';\n// Or if you need extra functionality\nimport extListen from 'listate/extra';\n```\n\n### Node\n\n```js\nconst listen = require('listate').listen;\n// Or if you need extra functionality\nconst extListen = require('listate/extra').listen;\n```\n\n### AMD\n\n```js\ndefine(['path/to/dist/listate.js', 'path/to/dist/extra.js'], function(listate, extra) {\n    const listen = listate.listen;\n    // Import extra.js if you need extra functionality\n    const extListen = extra.listen;\n});\n```\n\n### Bower, \u0026lt;script\u0026gt;\n\n```html\n\u003c!-- Use bower_components/listate/dist/listate.js and bower_components/listate/dist/extra.js if the library was installed by Bower --\u003e\n\u003cscript type=\"text/javascript\" src=\"path/to/dist/listate.js\"\u003e\u003c/script\u003e\n\u003c!-- Or if you need extra functionality --\u003e\n\u003cscript type=\"text/javascript\" src=\"path/to/dist/extra.js\"\u003e\u003c/script\u003e\n\u003cscript type=\"text/javascript\"\u003e\n    // listate is available via listate field of window object\n    const listen = listate.listen;\n    // Extra functionality is available inside extra namespace\n    const extListen = listate.extra.listen;\n\u003c/script\u003e\n```\n\n### Examples \u003ca name=\"examples\"\u003e\u003c/a\u003e [\u0026#x2191;](#start)\n\n```js\nimport { createStore } from 'redux';\nimport listen from 'listate';\nimport extListen from 'listate/extra';\n\nconst initState = {\n    user: null,\n    section: '',\n    map: {\n        main: {}\n    }\n};\n\nfunction reducer(state, action) {\n    const { payload } = action;\n    let newState;\n    switch (action.type) {\n        case 'AUTH':\n            return Object.assign({}, state, {user: payload});\n        case 'SELECT_SECTION':\n            return Object.assign({}, state, {section: payload});\n        case 'SET_SECTION':\n            newState = Object.assign({}, state);\n            newState.map = Object.assign({}, state.map);\n            newState.map[payload.key] = payload.value;\n            return newState;\n        default:\n            return state;\n    }\n}\n\nconst store = createStore(reducer, initState);\n\nlisten(store, {\n    data: 'main',\n    filter: (state) =\u003e state.user,\n    // One-time listener\n    once: true,\n    handle(data) {\n        // Dispatch any action\n        data.dispatch({\n            type: 'SELECT_SECTION',\n            // data.current === state.user, data.data === 'main'\n            payload: data.current.favoriteSection || localStorage.getItem('selectedSection') || data.data\n        });\n    }\n});\nlisten(store, {\n    filter: (state) =\u003e state.section,\n    when: (current, prev) =\u003e current !== prev \u0026\u0026 current !== 'exit',\n    // Call the listener no more frequently than once per second\n    delay: 1000,\n    handle(data) {\n        // data.current === state.section\n        localStorage.setItem('selectedSection', data.current);\n        console.log('Saved section: ', data.current);\n    }\n});\nlisten(store, {\n    description: 'map change listener',\n    context: true,\n    filter: (state) =\u003e state.map,\n    when: (current, prev, data) =\u003e current.stat \u0026\u0026 data.state.user \u0026\u0026 data.state.section === 'video',\n    handle(data) {\n        console.log('data.prev:', data.prev);   // {main: {}}\n        console.log('data.current:', data.current);   // {main: {}, stat: {a: 1}}\n        console.log('this.description:', this.description);   // map change listener\n    }\n});\nextListen(store, {\n    filter: {s: 'section', main: 'map.main'},\n    handle(data) {\n        console.log('extListen: data.prev -', data.prev);\n        console.log('extListen: data.current -', data.current);\n    }\n});\n...\nstore.dispatch({\n    type: 'AUTH',\n    payload: {login: 'commander'}\n});\n...\nstore.dispatch({\n    type: 'SELECT_SECTION',\n    payload: 'video'\n});\n...\nstore.dispatch({\n    type: 'SET_SECTION',\n    payload: {\n        key: 'stat',\n        value: {\n            a: 1\n        }\n    }\n});\n...\nstore.dispatch({\n    type: 'SELECT_SECTION',\n    payload: 'news'\n});\n...\nstore.dispatch({\n    type: 'SET_SECTION',\n    payload: {\n        key: 'main',\n        value: {\n            content: 'text'\n        }\n    }\n});\n```\n\n## API \u003ca name=\"api\"\u003e\u003c/a\u003e [\u0026#x2191;](#start)\n\n### Base functionality (listate, dist/listate.js)\n\n#### baseWhen(state, prevState): boolean\n\nChecks whether current value (state) is not equal previous value (state).\n\nReturns value of the following comparison: `state !== prevState`.\n\n#### listen(store, listener): Function\n\nAdds/registers state change listener for the given store.\n\nArguments:\n\n* `store: object` - Store for which listener should be added/registered.\n* `listener: Function | object` - Specifies listener that should be called on a state change.\nCan be a function or an object that defines listener settings/details.\n* `listener.handle: Function` - Listener that should be called on a state change.\n* `listener.context: boolean | object` (optional) - Specifies object that should be used as `this` value when calling the listener.\n* `listener.data: any` (optional) - Any data that should be passed into the listener.\n* `listener.delay: number` (optional) - Specifies that listener should be called after the given number of milliseconds\nhave elapsed. Works similar to `debounce`: when several requests for the listener call arrive during the specified period\nonly the last one will be applied after the timeout. `0` means that the listener should be called asynchronuosly.\n* `listener.filter: (state) =\u003e state.part` (optional) - Function (selector) to extract state part\nwhich will be used inside `when` to determine whether the listener should be called.\nBy default the entire state will be used.\n* `listener.once: boolean` (optional) - Whether the listener should be called just once (by default `false`).\n* `listener.when: (current, prev, data) =\u003e boolean` (optional) - Function to determine\nwhether the listener should be called. By default `baseWhen` is used.\nThe listener will be called if the function returns true.\nThe following parameters will be passed into the function:\n\n    - The current state or a part of the current state if `filter` is set.\n    - The previous state or a part of the previous state if `filter` is set.\n    - An object that will be passed into listener.\n\nReturns a function that removes/unsubscribes the listener.\n\nAn object with the following fileds will be passed as parameter into the listener:\n\n* `current: any` - The current state or a part of the current state if `filter` is set.\n* `prev: any` - The previous state or a part of the previous state if `filter` is set.\n* `state: object` - The current state.\n* `prevState: object` - The previous state.\n* `data: any` - The auxiliary data (value of `listener.data` parameter).\n* `store: object` - The store for which listener is registered.\n* `dispatch: Function` - Reference to `dispatch` method of the store.\n* `unlisten: Function` - The function that removes/unsubscribes the listener.\n\n### Extra functionality (listate/extra, dist/extra.js)\n\n#### getPathValue(obj, path): any\n\nReturn value of specified field path inside given object.\n\n```js\nimport { getPathValue } from 'listate/extra';\nconst obj = {\n    a: {\n        b: {\n           c: 'value'\n        },\n        d: true\n    },\n    e: 4,\n    f: [1, 'z', null]\n};\ngetPathValue(obj, 'a.b.c');   // 'value'\ngetPathValue(obj, 'a.c');   // undefined\n```\n\n#### getObjectPart(source, parts): object\n\nCreate an object containing specified parts of the given object.\n\n```js\nimport { getObjectPart } from 'listate/extra';\nconst obj = {\n    a: {\n        b: {\n           c: 'value',\n           d: true\n        },\n        e: 4,\n        f: [1, 'z', null]\n    },\n    g: 7,\n    h: {\n        i: false,\n        j: 0\n    },\n    k: 'king',\n    l: 'last'\n};\ngetObjectPart(obj, {f1: 'a.b.d', f2: 'a.f.1', f3: 'g', f4: 'h.j'});   // {f1: true, f2: 'z', f3: 7, f4: 0}\n```\n\n#### getFieldFilter(path): Function\n\nReturn a function that extracts value of the specified field path inside a given object.\n\n```js\nimport { getFieldFilter } from 'listate/extra';\nconst filter = getFieldFilter('a.d');\nconst obj = {\n    a: {\n        b: {\n           c: 'value'\n        },\n        d: 17\n    },\n    e: 4,\n    f: [1, 'z', null]\n};\nfilter(obj);   // 17\n```\n\n#### getPartFilter(parts): Function\n\nReturn a function that creates an object containing the specified parts of a given object.\n\n```js\nimport { getPartFilter } from 'listate/extra';\nconst filter = getPartFilter({f1: 'a.b.c', f2: 'h.j', f3: 'k'});\nconst obj = {\n    a: {\n        b: {\n           c: 'value',\n           d: true\n        },\n        e: 4,\n        f: [1, 'z', null]\n    },\n    g: 7,\n    h: {\n        i: false,\n        j: 0\n    },\n    k: 'king',\n    l: 'last'\n};\nfilter(obj);   // {f1: 'value', f2: 0, f3: 'king'}\n```\n\n#### unlike(state, prevState, deep): boolean\n\nCheck whether current object (state) is not equal previous object (state) comparing values of their fields.\n\n```js\nimport { unlike } from 'listate/extra';\nunlike({a: 1, b: 2}, {a: 1, b: 2});   // false\nunlike({a: 1, b: {c: 3}}, {a: 1, b: {c: 3}});   // true\nunlike({a: 1, b: {c: 3}}, {a: 1, b: {c: 3}}, true);   // false\n```\n\n#### unlikeDeep(state, prevState): boolean\n\nCheck whether current object (state) is not equal previous object (state) deeply comparing values of their fields.\n\nThe same as `unlike(state, prevState, true)`.\n\n#### listen(store, listener): Function\n\nAdd/register state change listener for the given store.\n\nIt is a wrap around base `listate.listen` that supports the following enhanced listener settings:\n\n* `listener.filter`.\nWhen an array or an object is passed, the used filter will be result of `getPartFilter(listener.filter)`.\nWhen a string is passed, the used filter will be result of `getFieldFilter(listener.filter)`.\n* `listener.when`. By default `unlike` is used.\n\nSee `doc` folder for details.\n\n## Contributing \u003ca name=\"contributing\"\u003e\u003c/a\u003e [\u0026#x2191;](#start)\nIn lieu of a formal styleguide, take care to maintain the existing coding style.\nAdd unit tests for any new or changed functionality.\nLint and test your code.\n\n## License \u003ca name=\"license\"\u003e\u003c/a\u003e [\u0026#x2191;](#start)\nCopyright (c) 2017-2019 Denis Sikuler  \nLicensed under the MIT license.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgamtiq%2Flistate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgamtiq%2Flistate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgamtiq%2Flistate/lists"}