{"id":18541305,"url":"https://github.com/cyclejs/collection","last_synced_at":"2025-08-25T17:04:53.031Z","repository":{"id":66270784,"uuid":"59455622","full_name":"cyclejs/collection","owner":"cyclejs","description":"An easier way to do collections in Cycle.js","archived":false,"fork":false,"pushed_at":"2018-12-14T02:55:52.000Z","size":169,"stargazers_count":60,"open_issues_count":10,"forks_count":10,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-08-15T18:47:22.236Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cyclejs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2016-05-23T05:54:55.000Z","updated_at":"2020-11-12T22:20:46.000Z","dependencies_parsed_at":null,"dependency_job_id":"d2f96ebe-92ab-48d3-963a-3d9c06e77cfc","html_url":"https://github.com/cyclejs/collection","commit_stats":{"total_commits":111,"total_committers":13,"mean_commits":8.538461538461538,"dds":0.4414414414414415,"last_synced_commit":"c27b9653de0b1f9cc2942cbba85dd95b8e7992dc"},"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/cyclejs/collection","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cyclejs%2Fcollection","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cyclejs%2Fcollection/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cyclejs%2Fcollection/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cyclejs%2Fcollection/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cyclejs","download_url":"https://codeload.github.com/cyclejs/collection/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cyclejs%2Fcollection/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272100683,"owners_count":24873442,"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","status":"online","status_checked_at":"2025-08-25T02:00:12.092Z","response_time":1107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":[],"created_at":"2024-11-06T20:04:41.123Z","updated_at":"2025-08-25T17:04:52.991Z","avatar_url":"https://github.com/cyclejs.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# cycle-collections\nCollection is no longer actively maintained. We recommend you check out [@cycle/state](https://cycle.js.org/api/state.html)\n---\n\nAn easier way to do collections in Cycle\n\nComponents can be hard to manage in Cycle.js. They can be especially painful when you're working with lists of components.\n\n`Collection` is a helper function that makes managing your lists of components a cinch.\n\nInstallation\n---\n```bash\n$ npm install @cycle/collection --save\n```\n\nHow does it work?\n---\n\n\u003c!-- share-code-between-examples --\u003e\n\n```js\nimport Collection from '@cycle/collection';\n```\n\nLet's say we have a `TodoListItem` component, and we want to make a `TodoList`.\n\n```js\nfunction TodoListItem (sources) {\n  // ...\n\n  return sinks;\n}\n```\n\nYou can make a collection stream by calling `Collection()` and passing it a component.\n\n```js\nconst todoListItems$ = Collection(TodoListItem);\n```\n\nIt's common in Cycle that you want to pass your `sources` to your children. You can pass a `sources` object as the second argument. Each item in the collection will be passed these sources.\n\n```js\nconst todoListItems$ = Collection(TodoListItem, sources);\n```\n\nTo actually populate the collection, you pass an `add$` stream. Its emitted values may be sources objects, which will be merged with the sources object you passed when you created the `collection$`. This is useful for passing `props$`. For Collection versions 0.6.0+ (based on Cycle Unified), any stream type supported by Cycle.js should work automatically. See the [old README](https://github.com/cyclejs/collection/tree/47e988ba5bf19d2a8172aba1f581200335f46b70#diversity) if you are older versions of Cycle.js (Cycle.js Diversity).\n\n```js\nconst todoListItems$ = Collection(TodoListItem, sources, xs.of(additionalSources));\n```\n\n`add$` can emit an array, if multiple items should be added at once.\n\n```js\nconst todoListItems$ = Collection(TodoListItem, sources, xs.of([firstSources, secondSources]));\n```\n\n`Collection()` returns a stream with arrays of items as values. Those arrays are cloned from internal ones, so changes will not impact the state of the `collection$`.\n\nCollections are **immutable**. This is because in Cycle.js values that change are represented as streams.\n\nIf we put it all together in our `TodoList` it looks like this:\n\n```js\nfunction TodoList (sources) {\n  const addTodo$ = sources.DOM\n    .select('.add-todo')\n    .events('click')\n    .mapTo(null); // to prevent adding click events as sources\n\n  const todoListItems$ = Collection(TodoListItem, sources, addTodo$);\n\n  const sinks = {\n    DOM: xs.of(\n      div('.todo-list', [\n        button('.add-todo', 'Add todo')\n      ])\n    )\n  }\n\n  return sinks;\n}\n```\n\nWait, how do we get the `todoListItems` to show up in the `DOM`?\n---\n\n`Collection.pluck` to the rescue!\n\n```js\nconst todoListItemVtrees$ = Collection.pluck(todoListItems$, item =\u003e item.DOM);\n```\n\n`Collection.pluck` takes a collection stream and a selector function and returns a stream of arrays of the latest value for each item. Selector function takes the sinks object and returns a stream. So for the `DOM` property each item in the stream is an array of vtrees. It handles the map/combine/flatten for you and also ensures that any vtree streams have unique keys on their values. This improves performance quite a bit and helps snabbdom tell the difference between each item.\n\nWe can now map over `todoListItemVtrees$` to display our todoListItems.\n\n```js\nfunction TodoList (sources) {\n  const addTodo$ = sources.DOM\n    .select('.add-todo')\n    .events('click')\n    .mapTo(null); // to prevent adding click events as sources\n\n  const todoListItems$ = Collection(TodoListItem, sources, addTodo$);\n\n  const todoListItemVtrees$ = Collection.pluck(todoListItems$, item =\u003e item.DOM);\n\n  const sinks = {\n    DOM: todoListItemVtrees$.map(vtrees =\u003e\n      div('.todo-list', [\n        button('.add-todo', 'Add todo'),\n\n        div('.items', vtrees)\n      ])\n    )\n  }\n\n  return sinks;\n}\n```\n\nBut wait, there's more!\n---\n\nThere is another common and hard to solve problem with lists in Cycle.js.\n\nSay our `TodoListItem` has a 'remove' button. What happens when you click it?\n\nA `TodoListItem` can't really remove itself. The state of the parent element needs to change.\n\nAll that a `TodoListItem` can do is return a `remove$` stream as part of it's `sinks`, along with `DOM`.\n\nNormally, to solve this problem you would need to create a circular reference between the sinks of the items in your collections and the stream of `reducers` you're `fold`ing over. This is achieved using `imitate` in `xs` or `Subject` in `rx`. This can be tricky code to write and read, and often adds quite a bit of boilerplate to your component.\n\nWhen you create a `Collection` you can optionally pass a `removeSelector` function that returns a stream which will trigger item's removal.\n\n```js\nconst todoListItems$ = Collection(TodoListItem, sources, add$, item =\u003e item.remove$);\n```\n\nAll together now!\n\n```js\nfunction TodoList (sources) {\n  const addTodo$ = sources.DOM\n    .select('.add-todo')\n    .events('click')\n    .mapTo(null); // to prevent adding click events as sources\n\n  const todoListItems$ = Collection(TodoListItem, sources, addTodo$, item =\u003e item.remove$);\n\n  const todoListItemVtrees$ = Collection.pluck(todoListItems$, item =\u003e item.DOM);\n\n  const sinks = {\n    DOM: todoListItemVtrees$.map(vtrees =\u003e\n      div('.todo-list', [\n        button('.add-todo', 'Add todo'),\n\n        div('.items', vtrees)\n      ])\n    )\n  }\n\n  return sinks;\n}\n```\n\nAnd how do we process fetched data?\n---\n\nIt's a quite common use case when a collection is built from fetched data. Usually it comes in a form of items' state snapshot. `Collection.gather` takes a stream of those snapshots and turns into a stream of collections. It takes `Collection` and `sources` arguments, just as `Collection` does, plus the snapshots stream `itemState$`, an optional `idAttribute` argument, which defaults to `'id'`, and an optional `transformKey` function for converting source keys.\n\n```js\nconst tasks$ = Collection.gather(Task, sources, fetchedTasks$, 'uid', key =\u003e `${key}$`) // converts 'props' in snapshots to 'props$' in sources\n```\n\nIt uses a set of rules:\n\n- items are keyed by `idAttribute`.\n- items that weren't present in the previous snapshot are added to collection.\n- each added item tracks it's own state, turning the sequence of each field's values into a source.\n- item is removed from collection if it's no more present in a snapshot.\n\nSo what if our components issue HTTP requests?\n---\n\nThere are kinds of sinks that rather represent actions than states. HTTP sink is a good example. If we want to get a stream of all HTTP requests issued by collection's items, `Collection.merge` will provide us one. It works basically the same as `Collection.pluck`, but merges the sinks instead of combining them into array.\n\n```js\nconst tasksRequest$ = Collection.merge(tasks$, item =\u003e item.HTTP);\n```\n\nImporting `Collection` directly is the same as calling `makeCollection()`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcyclejs%2Fcollection","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcyclejs%2Fcollection","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcyclejs%2Fcollection/lists"}