{"action":"opened","issue":{"url":"https://api.github.com/repos/headless-ninja/javascript/issues/41","repository_url":"https://api.github.com/repos/headless-ninja/javascript","labels_url":"https://api.github.com/repos/headless-ninja/javascript/issues/41/labels{/name}","comments_url":"https://api.github.com/repos/headless-ninja/javascript/issues/41/comments","events_url":"https://api.github.com/repos/headless-ninja/javascript/issues/41/events","html_url":"https://github.com/headless-ninja/javascript/issues/41","id":345358892,"node_id":"MDU6SXNzdWUzNDUzNTg4OTI=","number":41,"title":"Components are not thread-safe","user":{"login":"bartlangelaan","id":1851670,"node_id":"MDQ6VXNlcjE4NTE2NzA=","avatar_url":"https://avatars0.githubusercontent.com/u/1851670?v=4","gravatar_id":"","url":"https://api.github.com/users/bartlangelaan","html_url":"https://github.com/bartlangelaan","followers_url":"https://api.github.com/users/bartlangelaan/followers","following_url":"https://api.github.com/users/bartlangelaan/following{/other_user}","gists_url":"https://api.github.com/users/bartlangelaan/gists{/gist_id}","starred_url":"https://api.github.com/users/bartlangelaan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bartlangelaan/subscriptions","organizations_url":"https://api.github.com/users/bartlangelaan/orgs","repos_url":"https://api.github.com/users/bartlangelaan/repos","events_url":"https://api.github.com/users/bartlangelaan/events{/privacy}","received_events_url":"https://api.github.com/users/bartlangelaan/received_events","type":"User","site_admin":false},"labels":[{"id":662550982,"node_id":"MDU6TGFiZWw2NjI1NTA5ODI=","url":"https://api.github.com/repos/headless-ninja/javascript/labels/bug","name":"bug","color":"ee0701","default":true},{"id":1007111302,"node_id":"MDU6TGFiZWwxMDA3MTExMzAy","url":"https://api.github.com/repos/headless-ninja/javascript/labels/hn-react","name":"hn-react","color":"fbca04","default":false}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2018-07-27T20:36:47Z","updated_at":"2018-07-27T20:36:47Z","closed_at":null,"author_association":"CONTRIBUTOR","body":"At this moment, all components use a global `site` variable that is created in `hn-react`. In this `Site` instance all data is tracked.\r\n\r\nWhile this works fine client-side, this isn't ideal server-side. It is possible to `.reset()` and `.initialize()` the site before a server-side render to clean all data, but it isn't unthinkable that two clients request a page at the same time. If that happens, the requests conflict and overwrite each other. This means the components aren't thread-safe; it's possible that multiple threads conflict.\r\n\r\nThe only way to resolve this is to create a provider that can provide all components of a single site trough the React context. This way, on the server every request get's its own site.\r\n\r\n*Before*\r\n```jsx\r\nimport { site, DrupalPage } from 'hn-react';\r\nimport { render } from 'react-dom';\r\n\r\nsite.initialize({ url: '/' });\r\nrender(\r\n  <DrupalPage\r\n    location=\"https://headless.ninja\"\r\n    mapper={{}}\r\n  />,\r\n  document.body\r\n);\r\n```\r\n\r\n*After*\r\n```jsx\r\nimport { Site } from 'hn';\r\nimport { SiteProvider, DrupalPage } from 'hn-react';\r\nimport { render } from 'react-dom';\r\n\r\nconst site = new Site({ url: '/' });\r\n\r\nrender(\r\n  <SiteProvider site={site}>\r\n    <DrupalPage\r\n      location=\"https://headless.ninja\"\r\n      mapper={{}}\r\n    />\r\n  </SiteProvider>,\r\n  document.body\r\n);\r\n```\r\n\r\n\r\nThis also means that everywhere the `site` from `hn-react` is used, this must be replaced with a context-based site. For example on components where `site.getData()` is called:\r\n\r\n*Before*\r\n```jsx\r\nimport { site } from 'hn-react';\r\n\r\nclass AdvancedPageComponent extends Component {\r\n  render() {\r\n    const uuid = this.props.entity.uid.target_uuid;\r\n    const author = site.getData(uuid);\r\n    return (\r\n      <div>Author: {author.field_name}</div>\r\n    );\r\n  }\r\n}\r\n\r\nexport default AdvancedPageComponent;\r\n```\r\n\r\n*After (with higher-order component)*\r\n```jsx\r\nimport { withSite } from 'hn-react';\r\n\r\nclass AdvancedPageComponent extends Component {\r\n  render() {\r\n    const uuid = this.props.entity.uid.target_uuid;\r\n    const author = this.props.site.getData(uuid);\r\n    return (\r\n      <div>Author: {author.field_name}</div>\r\n    );\r\n  }\r\n}\r\n\r\nexport default withSite(AdvancedPageComponent);\r\n```\r\n\r\n*Or (using a consumer)*\r\n```jsx\r\nimport { Site } from 'hn-react';\r\n\r\nclass AdvancedPageComponent extends Component {\r\n  render() {\r\n    const uuid = this.props.entity.uid.target_uuid;\r\n    return (\r\n      <Site>\r\n        {site => (\r\n          <div>Author: {site.getData(uuid).field_name}</div>\r\n        )}\r\n      </Site>\r\n    );\r\n  }\r\n}\r\n\r\nexport default AdvancedPageComponent;\r\n```\r\n\r\nOf course, this should all be backwards compatible. If no context can be found, the global `site` should be used in all components."}}
{"actor":{"display_login":"bartlangelaan"}}