{"action":"created","issue":{"url":"https://api.github.com/repos/reactjs/reselect/issues/62","repository_url":"https://api.github.com/repos/reactjs/reselect","labels_url":"https://api.github.com/repos/reactjs/reselect/issues/62/labels{/name}","comments_url":"https://api.github.com/repos/reactjs/reselect/issues/62/comments","events_url":"https://api.github.com/repos/reactjs/reselect/issues/62/events","html_url":"https://github.com/reactjs/reselect/issues/62","id":116952447,"number":62,"title":"memoize map/fmap","user":{"login":"acthp","id":494506,"avatar_url":"https://avatars.githubusercontent.com/u/494506?v=3","gravatar_id":"","url":"https://api.github.com/users/acthp","html_url":"https://github.com/acthp","followers_url":"https://api.github.com/users/acthp/followers","following_url":"https://api.github.com/users/acthp/following{/other_user}","gists_url":"https://api.github.com/users/acthp/gists{/gist_id}","starred_url":"https://api.github.com/users/acthp/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/acthp/subscriptions","organizations_url":"https://api.github.com/users/acthp/orgs","repos_url":"https://api.github.com/users/acthp/repos","events_url":"https://api.github.com/users/acthp/events{/privacy}","received_events_url":"https://api.github.com/users/acthp/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":6,"created_at":"2015-11-14T21:16:33Z","updated_at":"2016-04-08T14:24:12Z","closed_at":null,"body":"A common pattern in my app state is computed properties on dictionary elements. E.g. building an index of data that is identified by a key.\r\n\r\nApp state might look like\r\n\r\n```\r\n{ '1234-0001': arr1, '1234-0002': arr2, ... }\r\n```\r\n\r\nand the selector returns something like\r\n\r\n```\r\n{'1234-0001': makeExpensiveIndex(arr1), '1234-0002': makeExpensiveIndex(arr2), ... }\r\n```\r\n\r\nwhich might be done like ```_.mapObject(state, makeExpensiveIndex)```. You don't want to recompute the index on arr2 if only arr1 has changed.\r\n\r\nAt first glance it looks like reselect can't manage this case. Is there a simple way to do it? Or a different way of modeling dynamic data?\r\n\r\nSimilar patterns can happen with arrays, but it's harder to handle since we can't hash on object values in javascript. You have to either cache by index (position in the array), or by some unique property on the elements in the array (like the ```key``` prop in react element collections).\r\n"},"comment":{"url":"https://api.github.com/repos/reactjs/reselect/issues/comments/207451364","html_url":"https://github.com/reactjs/reselect/issues/62#issuecomment-207451364","issue_url":"https://api.github.com/repos/reactjs/reselect/issues/62","id":207451364,"user":{"login":"arcanis","id":1037931,"avatar_url":"https://avatars.githubusercontent.com/u/1037931?v=3","gravatar_id":"","url":"https://api.github.com/users/arcanis","html_url":"https://github.com/arcanis","followers_url":"https://api.github.com/users/arcanis/followers","following_url":"https://api.github.com/users/arcanis/following{/other_user}","gists_url":"https://api.github.com/users/arcanis/gists{/gist_id}","starred_url":"https://api.github.com/users/arcanis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/arcanis/subscriptions","organizations_url":"https://api.github.com/users/arcanis/orgs","repos_url":"https://api.github.com/users/arcanis/repos","events_url":"https://api.github.com/users/arcanis/events{/privacy}","received_events_url":"https://api.github.com/users/arcanis/received_events","type":"User","site_admin":false},"created_at":"2016-04-08T14:24:12Z","updated_at":"2016-04-08T14:24:12Z","body":"I've started to experiment with the following code, what do you think? I'm somewhat mixed, because it dynamically adds a new property to some objects, which may hurt the JS engine optimizations, but API-wise, it seems quite elegant.\r\n\r\n```js\r\nlet customComparator = (a, b) => {\r\n    return a.length === b.length && a.every(aVal => b.find(bVal => aVal === bVal));\r\n};\r\n\r\nlet getStreamModels = createSelector([\r\n    (state, props) => useCustomCheck(customComparator, state.stream.map(locator => state.resourceRegistry.get(locator)),\r\n], resources => {\r\n    return resources;\r\n})\r\n```\r\n\r\n```js\r\nimport { createSelectorCreator, defaultMemoize } from 'reselect';\r\n\r\nlet customCheckSymbol = Symbol();\r\n\r\nexport function useCustomCheck(check, value) {\r\n\r\n    value[customCheckSymbol] = check;\r\n\r\n    return value;\r\n\r\n}\r\n\r\nexport function getCustomCheck(value) {\r\n\r\n    return value[customCheckSymbol];\r\n\r\n}\r\n\r\nexport function usesCustomCheck(value) {\r\n\r\n    return Reflect.has(value, customCheckSymbol);\r\n\r\n}\r\n\r\nexport let createSelector = createSelectorCreator(defaultMemoize, (a, b) => {\r\n\r\n    if (a === b)\r\n        return true;\r\n\r\n    if (usesCustomCheck(a) && usesCustomCheck(b)) {\r\n\r\n        let aCheck = getCustomCheck(a);\r\n        let bCheck = getCustomCheck(b);\r\n\r\n        if (aCheck !== bCheck)\r\n            throw new Error();\r\n\r\n        return aCheck(a, b);\r\n\r\n    } else {\r\n\r\n        return false;\r\n\r\n    }\r\n\r\n});\r\n```\r\n"}}