{"id":22878830,"url":"https://github.com/taskworld/rereselect","last_synced_at":"2025-05-07T00:29:24.227Z","repository":{"id":33117535,"uuid":"145705727","full_name":"taskworld/rereselect","owner":"taskworld","description":"A library that generates memoized selectors (like Reselect) which supports dynamic dependency tracking (à la Vue/VueX/MobX).","archived":false,"fork":false,"pushed_at":"2024-07-03T09:29:35.000Z","size":1219,"stargazers_count":24,"open_issues_count":17,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-31T04:32:45.708Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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/taskworld.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-08-22T12:25:01.000Z","updated_at":"2025-03-21T14:00:39.000Z","dependencies_parsed_at":"2024-12-13T16:42:54.583Z","dependency_job_id":"dd7f20fa-8787-4e1c-a67d-fd4e174e7e20","html_url":"https://github.com/taskworld/rereselect","commit_stats":{"total_commits":41,"total_committers":3,"mean_commits":"13.666666666666666","dds":0.07317073170731703,"last_synced_commit":"0cf96e9043c983a4f76c75046f76b56f25f1086c"},"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/taskworld%2Frereselect","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/taskworld%2Frereselect/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/taskworld%2Frereselect/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/taskworld%2Frereselect/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/taskworld","download_url":"https://codeload.github.com/taskworld/rereselect/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251857024,"owners_count":21655121,"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":[],"created_at":"2024-12-13T16:32:46.273Z","updated_at":"2025-05-07T00:29:24.207Z","avatar_url":"https://github.com/taskworld.png","language":"TypeScript","readme":"# rereselect\n\n\u003e Not to be confused with\n\u003e [Re-reselect](https://github.com/toomuchdesign/re-reselect) which is an\n\u003e enhancement to [Reselect](https://github.com/reduxjs/reselect). This is an\n\u003e entirely separate project.\n\nA library that generates memoized selectors like\n[Reselect](https://github.com/reduxjs/reselect) but:\n\n- **Supports dynamic dependency tracking** à la Vue/VueX/MobX. Read\n  [the motivation section](#motivation) to see why we need this.\n- No need to declare upfront which selectors will be used.\n- Introspection (hooks) API baked in to help debug performance problems.\n\n**Design constraints:**\n\n- Generated selector must be compatible with Reselect.\n\n**Notes:**\n\n- The running environment must be at least ECMAScript 6 (ES2015).\n- TypeScript typings are available.\n- The state must be immutable.\n- The selector logic must be pure and deterministic.\n- rereselect’s selectors take 1 argument only — the state. If you need\n  parameterized selectors, see the section\n  [parameterized selectors](#parameterized-selectors).\n- _Zero maintenance support_. This library is created to solve the problems we face. We\n  open-source it in hope that it will be useful to others as well, but we have\n  no plans in supporting it beyond our use cases. Therefore, feature requests\n  are not accepted here.\n  \n## Installation\n\n```\nnpm install @taskworld.com/rereselect\n```\n\n## Motivation\n\nWhy a new selector library?\n\nHere’s an example. Let’s say we have a list of online user IDs and a mapping\nfrom user ID to user’s information. We want to select a list of online users, as\nin this example:\n\n![A selector that selects a list of online users. It depends on `state.onlineUserIds` and for each user ID in the latter, `state.users[name]`.](./docs/images/fine-grained.png)\n\nCreating such selector is impossible using Reselect, because in Reselect,\n**selectors must declare their dependencies statically upfront.** Since we\ncouldn’t know in advance which users will be online, we need to declare a\ndependency on the whole `users` branch of the state tree:\n\n![Instead of depending only on relevant users, we had to depend on the whole `state.users` branch.](./docs/images/limitation.png)\n\nThis results in a selector that looks like this:\n\n```js\nconst selectOnlineUsers = createSelector(\n  state =\u003e state.onlineUserIds,\n  state =\u003e state.users,\n  (onlineUserIds, users) =\u003e {\n    return onlineUserIds.map(id =\u003e users[id])\n  }\n)\n```\n\nThis works, but this means that changes to unrelated users (`bob`, `charlie`,\n`eve`) will cause the selector to be recomputed. This problem has been\n[asked](https://stackoverflow.com/q/50965013/559913)\n[multiple](https://github.com/reduxjs/reselect/issues/353)\n[times](https://github.com/reduxjs/reselect/issues/360) with no efficient and\nelegant solution.\n\nWith **rereselect**, selectors don’t declare their dependencies upfront.\nInstead, they are inlined in the selection logic:\n\n```js\nconst selectOnlineUsers = makeSelector(query =\u003e {\n  const userIds = query(state =\u003e state.onlineUserIds)\n  return userIds.map(id =\u003e query(state =\u003e state.users[id]))\n})\n```\n\nThe selection logic will receive a function `query` which can be used to invoke\nother selectors. In doing so, the dependency will be tracked automatically. This\nallows more fine-grained control over which part of the state tree would cause\nthe selector to be recomputed.\n\n![](./docs/images/example-usage.png)\n\n## Differences from Reselect\n\nThe Reselect “shopping cart” example:\n\n```js\nimport { makeSelector } from '@taskworld.com/rereselect'\n\n// “Simple” selectors are the same.\nconst shopItemsSelector = state =\u003e state.shop.items\nconst taxPercentSelector = state =\u003e state.shop.taxPercent\n\n// Instead of `createSelector`, it is called `makeSelector`.\n//\n// Instead of declaring dependencies upfront, use the `query` function\n// to invoke other selectors. In doing so, the dependency will\n// automatically be tracked.\n//\nconst subtotalSelector = makeSelector(query =\u003e\n  query(shopItemsSelector).reduce((acc, item) =\u003e acc + item.value, 0)\n)\nconst taxSelector = makeSelector(\n  query =\u003e query(subtotalSelector) * (query(taxPercentSelector) / 100)\n)\nconst totalSelector = makeSelector(query =\u003e ({\n  total: query(subtotalSelector) + query(taxSelector),\n}))\n```\n\nDynamic dependency tracking:\n\n```js\nlet state = {\n  fruits: {\n    a: { name: 'Apple' },\n    b: { name: 'Banana' },\n    c: { name: 'Cantaloupe' },\n  },\n  selectedFruitIds: ['a', 'c'],\n}\n\n// I want to query the selected fruits...\nconst selectSelectedFruits = makeSelector(query =\u003e\n  query(state =\u003e state.selectedFruitIds).map(id =\u003e\n    query(state =\u003e state.fruits[id])\n  )\n)\n\n// Use like any other selectors:\nconsole.log(selectSelectedFruits(state)) // [ { name: 'Apple' }, { name: 'Cantaloupe' } ]\n\n// Since data selection is fine-grained, changes to unrelated parts\n// of the state will not cause a recomputation.\nstate = {\n  ...state,\n  fruits: {\n    ...state.fruits,\n    b: { name: 'Blueberry' },\n  },\n}\nconsole.log(selectSelectedFruits(state)) // [ { name: 'Apple' }, { name: 'Cantaloupe' } ]\nconsole.log(selectSelectedFruits.recomputations()) // 1\n```\n\nReimplementing Reselect’s `createSelector` on top of `rereselect`:\n\n```js\nfunction createSelector(...funcs) {\n  const resultFunc = funcs.pop()\n  const dependencies = Array.isArray(funcs[0]) ? funcs[0] : funcs\n  return makeSelector(query =\u003e resultFunc(...dependencies.map(query)))\n}\n```\n\n### Performance comparison\n\nIn “cache hit” scenarios, **rereselect** is faster than **Reselect**. The numbers below are in million-operation-per-second.\n\n| Scenario | Reselect v3 | Reselect v5 | rereselect |\n|---|---|---|---|\n| cache hit (same state) | 15.6 | 16.6 | 17.1 |\n| cache hit (shallowly equal deps) | 5.5 | 1.1 | 7.6~9.8 |\n| cache miss | 3.9 | 0.5 | 3.8 |\n\n## Build your own abstraction\n\nThis library is only concerned with creating a selector system that supports\ndynamic dependency tracking. It provides a building blocks for which\nhigher-level abstractions can be built upon. So, it is up to you to built your\ntooling on top of this.\n\nPlease [read the test](src/index.test.ts) to see some of the real-world usage\nscenarios.\n\n### Parameterized selectors\n\nThis is how we do it (we also added `displayName` property to our selectors to\nmake them easier to debug):\n\n```typescript\nexport function makeParameterizedSelector(\n  displayName,\n  selectionLogicGenerator\n) {\n  const memoized = new Map()\n  return Object.assign(\n    function selectorFactory(...args) {\n      const key = args.join(',')\n      if (memoized.has(key)) return memoized.get(key)!\n      const name = `${displayName}(${key})`\n      const selectionLogic = selectionLogicGenerator(...args)\n      const selector = makeSelector(selectionLogic)\n      selector.displayName = name\n      memoized.set(key, selector)\n      return selector\n    },\n    { displayName }\n  )\n}\n```\n\n## API\n\nPlease [read the test](src/index.test.ts).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftaskworld%2Frereselect","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftaskworld%2Frereselect","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftaskworld%2Frereselect/lists"}