{"action":"created","issue":{"url":"https://api.github.com/repos/facebook/react/issues/3398","labels_url":"https://api.github.com/repos/facebook/react/issues/3398/labels{/name}","comments_url":"https://api.github.com/repos/facebook/react/issues/3398/comments","events_url":"https://api.github.com/repos/facebook/react/issues/3398/events","html_url":"https://github.com/facebook/react/issues/3398","id":60965610,"number":3398,"title":"Implement Sideways Data Loading","user":{"login":"sebmarkbage","id":63648,"avatar_url":"https://avatars.githubusercontent.com/u/63648?v=3","gravatar_id":"","url":"https://api.github.com/users/sebmarkbage","html_url":"https://github.com/sebmarkbage","followers_url":"https://api.github.com/users/sebmarkbage/followers","following_url":"https://api.github.com/users/sebmarkbage/following{/other_user}","gists_url":"https://api.github.com/users/sebmarkbage/gists{/gist_id}","starred_url":"https://api.github.com/users/sebmarkbage/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sebmarkbage/subscriptions","organizations_url":"https://api.github.com/users/sebmarkbage/orgs","repos_url":"https://api.github.com/users/sebmarkbage/repos","events_url":"https://api.github.com/users/sebmarkbage/events{/privacy}","received_events_url":"https://api.github.com/users/sebmarkbage/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":1,"created_at":"2015-03-13T02:37:05Z","updated_at":"2015-03-13T03:41:48Z","closed_at":null,"body":"This is a first-class API for sideways data loading of stateless (although potentially memoized) data from a global store/network/resource, potentially using props/state as input.\r\n\r\n```js\r\ntype RecordOfObservables = { [key:string]: Observable<mixed> };\r\n\r\nclass Foo {\r\n\r\n  observe(): RecordOfObservables {\r\n    return {\r\n      myContent: xhr(this.props.url)\r\n    };\r\n  }\r\n\r\n  render() {\r\n    var myContent : ?string = this.data.myContent;\r\n    return <div>{myContent}</div>;\r\n  }\r\n\r\n}\r\n```\r\n\r\nobserve() executes after componentWillMount/componentWillUpdate but before render.\r\n\r\nFor each key/value in the record. Subscribe to the Observable in the value.\r\n\r\n```js\r\nsubscription = observable.subscribe({ onNext: handleNext });\r\n```\r\n\r\nWe allow onNext to be synchronously invoked from subscribe. If it is, we set:\r\n\r\n```js\r\nthis.data[key] = nextValue;\r\n```\r\n\r\nOtherwise we leave it as undefined for the initial render. (Maybe we set it to null?)\r\n\r\nThen render proceeds as usual.\r\n\r\nEvery time onNext gets invoked, we schedule a new \"this.data[key]\" which effectively triggers a forcedUpdate on this component. If this is the only change, then observe is not reexecuted (componentWillUpdate -> render -> componentDidUpdate).\r\n\r\nIf props / state changed (i.e. an update from recieveProps or setState), then observe() is reexecuted (during reconciliation).\r\n\r\nAt this point we loop over the new record, and subscribe to all the new Observables.\r\n\r\nAfter that, unsubscribe to the previous Observables.\r\n\r\n```js\r\nsubscription.dispose();\r\n```\r\n\r\nThis ordering is important since it allows the provider of data to do reference counting of their cache. I.e. I can cache data for as long as nobody listens to it. If I unsubscribed immediately, then the reference count would go down to zero before I subscribe to the same data again.\r\n\r\nWhen a component is unmounted, we automatically unsubscribe from all the active subscriptions.\r\n\r\nIf the new subscription didn't immediately call onNext, then we will keep using the previous value.\r\n\r\nSo if my `this.props.url` from my example changes, and I'm subscribing to a new URL, myContent will keep showing the content of the previous url until the next url has fully loaded.\r\n\r\nThis has the same semantics as the `<img />` tag. We've seen that, while this can be confusing and lead to inconsistencies it is a fairly sane default, and it is easier to make it show a spinner than it would be to have the opposite default.\r\n\r\nBest practice might be to immediately send a \"null\" value if you don't have the data cached. Another alternative is for an Observable to provide both the URL (or ID) and the content in the result.\r\n\r\n```js\r\nclass Foo {\r\n\r\n  observe() {\r\n    return {\r\n      user: loadUser(this.props.userID)\r\n    };\r\n  }\r\n\r\n  render() {\r\n    if (this.data.user.id !== this.props.userID) {\r\n      // Ensure that we never show inconsistent userID / user.name combinations.\r\n      return <Spinner />;\r\n    }\r\n    return <div>Hello, {this.data.user.name} [{this.props.userID}]!</div>;\r\n  }\r\n\r\n}\r\n```\r\n\r\nWe should use the RxJS contract of Observable since that is more in common use and allows synchronous execution, but once @jhusain's proposal is in more common use, we'll switch to that contract instead.\r\n\r\n```js\r\nvar subscription = observable.subscribe({ onNext, onError, onCompleted });\r\nsubscription.dispose();\r\n```\r\n\r\nWe can add more life-cycle hooks that respond to these events if necessary.\r\n\r\nNote: This concept allows sideways data to behave like \"behaviors\" - just like props. This means that we don't have to overload the notion state for these things. It allows for optimizations such as throwing away the data only to resubscribe later. It is restorable."},"comment":{"url":"https://api.github.com/repos/facebook/react/issues/comments/78778030","html_url":"https://github.com/facebook/react/issues/3398#issuecomment-78778030","issue_url":"https://api.github.com/repos/facebook/react/issues/3398","id":78778030,"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},"created_at":"2015-03-13T03:41:48Z","updated_at":"2015-03-13T03:41:48Z","body":"`undefined` is probably the safest value to assign to `data` until the observable provides its first value via `onNext`. For example in Relay we assign different meanings to `null` (data does not exist) and `undefined` (not yet fetched), so our ideal default data value would be `undefined`. The alternative is to provide a new method, eg `getInitialData`, but I suspect this is unnecessary/overkill."}}