{"action":"created","issue":{"url":"https://api.github.com/repos/cyclejs/core/issues/204","repository_url":"https://api.github.com/repos/cyclejs/core","labels_url":"https://api.github.com/repos/cyclejs/core/issues/204/labels{/name}","comments_url":"https://api.github.com/repos/cyclejs/core/issues/204/comments","events_url":"https://api.github.com/repos/cyclejs/core/issues/204/events","html_url":"https://github.com/cyclejs/core/issues/204","id":129532394,"number":204,"title":"How to avoid re-creation/evaluation of components?","user":{"login":"josephsavona","id":6425824,"avatar_url":"https://avatars.githubusercontent.com/u/6425824?v=3","gravatar_id":"","url":"https://api.github.com/users/josephsavona","html_url":"https://github.com/josephsavona","followers_url":"https://api.github.com/users/josephsavona/followers","following_url":"https://api.github.com/users/josephsavona/following{/other_user}","gists_url":"https://api.github.com/users/josephsavona/gists{/gist_id}","starred_url":"https://api.github.com/users/josephsavona/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/josephsavona/subscriptions","organizations_url":"https://api.github.com/users/josephsavona/orgs","repos_url":"https://api.github.com/users/josephsavona/repos","events_url":"https://api.github.com/users/josephsavona/events{/privacy}","received_events_url":"https://api.github.com/users/josephsavona/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/cyclejs/core/labels/discussion","name":"discussion","color":"fef2c0"}],"state":"closed","locked":false,"assignee":null,"milestone":null,"comments":14,"created_at":"2016-01-28T18:18:02Z","updated_at":"2016-02-27T01:14:00Z","closed_at":"2016-02-25T08:42:28Z","body":"I was reading over Cycle's implementation and noticed an unexpected behavior while trying out some of the examples: it seems that components are re-created any time their parent changes, causing significant portions of the reactive computation graph to be discarded and recreated more often than seems necessary. This is easier to explain by example.\r\n\r\n### Example: TodoMVC\r\n\r\n1. Open the [Cycle todomvc example](https://github.com/cyclejs/todomvc-cycle/) example app.\r\n2. Create two new todos.\r\n3. Breakpoint the [`TodoItem`](https://github.com/cyclejs/todomvc-cycle/blob/master/src/components/Todos/view.js#L78) component definition. \r\n4. Make *any* change to *any* todo, and observe that *all* TodoItem components are discarded and re-rendered from scratch.\r\n\r\nConceptually, after step 2 the graph of reactive values looks something like:\r\n\r\n```\r\ntodo a -----\\\r\n             todos ---> app\r\ntodo b -----/\r\n``` \r\n\r\nBased on my understanding of the documentation, if only todo \"a\" changes `TodoItem` would not be re-evaulated for todo \"b\", and that portion of the graph to be reused. In practice, this isn't the case and both components are recreated and re-rendered (to virtual dom). I also expected that adding a new todo would not cause todo's \"a\" and \"b\" to be created or even re-rendered, but they are.\r\n\r\n### Generalization\r\n\r\nMore generally, this problem would seem to occur in *any* Cycle app that follows the documented patterns for constructing applications. In the [component documentation](http://cycle.js.org/components.html), for example, the BMI calculator's view function is as follows:\r\n\r\n```javascript\r\nconst vtree$ = Observable.combineLatest(sources.props$, value$,\r\n    (props, value) =>\r\n      div('.labeled-slider', [\r\n        // ... abbreviated\r\n        input('.slider', {\r\n          type: 'range', min: props.min, max: props.max, value\r\n        })\r\n      ])\r\n  );\r\n```\r\n\r\nBecause `input` immediately invokes the function and creates the component, there doesn't seem to be any way for an application to reuse a previous `input` at the same point in the graph (short of some memoization on the side).\r\n\r\n### Questions\r\n\r\nThis seems like a potentially significant performance problem for applications. If `TodoItem` were instead a complex component - i.e. with a large number of (indirect) subcomponents - it would be expensive to re-construct frequently. Note that React solves this problem by using descriptors - the equivalent of the BMI `input` call in React would be to create a description of the input component and its arguments, which would allow React to reuse the existing component if it existed based on the DOM structure and `key`. Cycle DOM theoretically supports this same optimization - it's supported by the underlying `virtual-dom` diffing algorithm - but this logic exists only in the driver, which is abstracted from the view. It isn't immediately clear how a Cycle app could be changed to take full advantage of `virtual-dom`'s component reuse in order to avoid recreating the components as React would.\r\n\r\nSome questions:\r\n- Is this an intentional design decision? \r\n- What are the benefits of always recalculating the components?\r\n- What is the suggested pattern for avoiding this type of reevaluation of components should it become a performance problem?\r\n\r\nI don't mean to attack Cycle by posting this issue, and I should note that this same problem can occur in React applications as well. I once debugged an issue where a complex React component was constantly re-rendering, taking 100s of milliseconds each time, all because of a callback prop being reallocated by its parent on every render. Fixing this meant that the component's `shouldComponentUpdate` worked again and that rendering could be skipped unless it was strictly necessary. I bring this up to point out that performance problems can occur in any architecture, and I'm wondering what the equivalent of React's `shouldComponentUpdate` performance hook is in Cycle."},"comment":{"url":"https://api.github.com/repos/cyclejs/core/issues/comments/189545542","html_url":"https://github.com/cyclejs/core/issues/204#issuecomment-189545542","issue_url":"https://api.github.com/repos/cyclejs/core/issues/204","id":189545542,"user":{"login":"ronag","id":3065230,"avatar_url":"https://avatars.githubusercontent.com/u/3065230?v=3","gravatar_id":"","url":"https://api.github.com/users/ronag","html_url":"https://github.com/ronag","followers_url":"https://api.github.com/users/ronag/followers","following_url":"https://api.github.com/users/ronag/following{/other_user}","gists_url":"https://api.github.com/users/ronag/gists{/gist_id}","starred_url":"https://api.github.com/users/ronag/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ronag/subscriptions","organizations_url":"https://api.github.com/users/ronag/orgs","repos_url":"https://api.github.com/users/ronag/repos","events_url":"https://api.github.com/users/ronag/events{/privacy}","received_events_url":"https://api.github.com/users/ronag/received_events","type":"User","site_admin":false},"created_at":"2016-02-27T01:14:00Z","updated_at":"2016-02-27T01:14:00Z","body":"@josephsavona: Here is my solution for this problem.\r\n\r\nUsing: https://gist.github.com/ronag/3db1f020416b895f7519\r\n\r\nYou can do:\r\n\r\n```javascript\r\nimport { resolve, resolveCombineLatest } from './rx-resolve'\r\n\r\nfunction TodoList(todos$) {\r\n  const todos$ = resolve.call(todos$, todo => (...))\r\n  return {\r\n    DOM: resolveCombineLatest.call(todos$, todo => todo.DOM, todos => \r\n      div('#todos', null, todos.map(todo => (...)) \r\n    ),\r\n  };\r\n}\r\n```\r\n\r\nThis way nothing will be unnecessarily re-allocated."}}