{"action":"created","issue":{"url":"https://api.github.com/repos/hyperapp/hyperapp/issues/750","repository_url":"https://api.github.com/repos/hyperapp/hyperapp","labels_url":"https://api.github.com/repos/hyperapp/hyperapp/issues/750/labels{/name}","comments_url":"https://api.github.com/repos/hyperapp/hyperapp/issues/750/comments","events_url":"https://api.github.com/repos/hyperapp/hyperapp/issues/750/events","html_url":"https://github.com/hyperapp/hyperapp/issues/750","id":354753116,"node_id":"MDU6SXNzdWUzNTQ3NTMxMTY=","number":750,"title":"V2 Effects API","user":{"login":"jorgebucaran","id":56996,"node_id":"MDQ6VXNlcjU2OTk2","avatar_url":"https://avatars1.githubusercontent.com/u/56996?v=4","gravatar_id":"","url":"https://api.github.com/users/jorgebucaran","html_url":"https://github.com/jorgebucaran","followers_url":"https://api.github.com/users/jorgebucaran/followers","following_url":"https://api.github.com/users/jorgebucaran/following{/other_user}","gists_url":"https://api.github.com/users/jorgebucaran/gists{/gist_id}","starred_url":"https://api.github.com/users/jorgebucaran/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jorgebucaran/subscriptions","organizations_url":"https://api.github.com/users/jorgebucaran/orgs","repos_url":"https://api.github.com/users/jorgebucaran/repos","events_url":"https://api.github.com/users/jorgebucaran/events{/privacy}","received_events_url":"https://api.github.com/users/jorgebucaran/received_events","type":"User","site_admin":false},"labels":[{"id":522275188,"node_id":"MDU6TGFiZWw1MjIyNzUxODg=","url":"https://api.github.com/repos/hyperapp/hyperapp/labels/Discussion","name":"Discussion","color":"7057ff","default":false},{"id":522275186,"node_id":"MDU6TGFiZWw1MjIyNzUxODY=","url":"https://api.github.com/repos/hyperapp/hyperapp/labels/Docs","name":"Docs","color":"7057ff","default":false}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":6,"created_at":"2018-08-28T14:18:38Z","updated_at":"2018-08-30T01:14:44Z","closed_at":null,"author_association":"MEMBER","body":"## Summary\r\n\r\nManaged effects or just _effects_ is V2's declarative approach to running side effects and asynchronous operations. Elm calls them commands, we call them effects.\r\n\r\nIn this issue I'll introduce you to the new Effects API in Hyperapp. I'll explain what effects are and how to create, use and implement them.\r\n\r\n## Background\r\n\r\nEffects are analogous to the virtual DOM. Think of them as virtual \"jobs\" or \"tasks\".\r\n\r\nAs you should know by now, a virtual DOM is a plain object, _not_ an actual DOM tree. Similarly, effects describe [side effects](<https://en.wikipedia.org/wiki/Side_effect_(computer_science)>), but _don't_ actually run any code. They are represented as plain objects too. Creating an effect doesn't change your environment or require you to use async functions, promises or callbacks. Effects by themselves are like source code without an interpreter/compiler.\r\n\r\nMuch the same way Hyperapp turns your view into an actual DOM (saving you from using the DOM APIs), effects are executed and reduced to a result which is eventually delivered to an action saving you from dealing with asynchronous flow or managing side effects yourself.\r\n\r\nEffects may require more than a single action to function, e.g., an HTTP fetch effect may expect a second action which should be run if there is an error with the request.\r\n\r\nEffects are usually, but not limited to, asynchronous operations. Here are some examples: creating HTTP requests, creating time delays, generating random numbers, [pushing or replacing](https://developer.mozilla.org/en-US/docs/Web/API/History_API) a URL to/from [Window.history](https://developer.mozilla.org/en-US/docs/Web/API/History), writing and reading to/from local/session [storage](https://developer.mozilla.org/en-US/docs/Web/API/Storage), opening/closing a database connection as well as writing and reading to it using [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API), requesting/exiting [fullscreen](https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API), [enqueing data](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/send) over a WebSocket, etc.\r\n\r\n## Declarative effects\r\n\r\nEffects are created through a function that returns an effect object.\r\n\r\n```js\r\nconst effectObject = MyEffect({\r\n  action: MyAction,\r\n  whatever: \"data\"\r\n})\r\n```\r\n\r\nMyEffect returns an object like this. \r\n\r\n```js\r\n{\r\n  effect: effectImplementation,\r\n  action: MyAction,\r\n  whatever: \"data\"\r\n}\r\n```\r\n\r\nCreating an effect this way does not actually execute the effect. Like a promise object that represents the eventual completion (or failure) of an operation, the effect object is waiting to be evaluated.\r\n\r\nEvery property should be self-explanatory, except for `effect`. What is `effectImplementation`? It's the function that encapsulates the side effect logic that Hyperapp will execute to consume the effect.\r\n\r\nMost users will never deal with this unless they are implementing their own custom effects. If you want to understand how effects work under the hood read [implementing custom effects](#implementing-custom-effects) later in this issue.\r\n\r\nNow that we have an effect, how do we tell Hyperapp to run it? We need to return it from an action. If you are not familiar with the V2 Action API yet see #749.\r\n\r\n```jsx\r\nconst RunMyEffect = state => [\r\n  state,\r\n  MyEffect({\r\n    action: MyAction,\r\n    whatever: \"data\"\r\n  })\r\n]\r\n```\r\n\r\nAnd if we want to parallelize/run multiple effects?\r\n\r\n```jsx\r\nconst RunMyEffect = state => [\r\n  state,\r\n  [\r\n    MyEffect({\r\n      action: MyAction,\r\n      whatever: \"data\"\r\n    }),\r\n    AnotherEffect({\r\n      action: AnotherAction,\r\n      nevermind: \"data\"\r\n    })\r\n  ]\r\n]\r\n```\r\n\r\n### Time delays\r\n\r\nLet's study an example using [setTimeout](https://developer.mozilla.org/ro/docs/Web/API/window.setTimeout).\r\n\r\nThis is how we could use setTimeout in V1.\r\n\r\n```jsx\r\nimport { h, app } from \"hyperapp\"\r\nimport { state, actions } from \"./app\"\r\n\r\nconst main = app({\r\n  state,\r\n  actions\r\n  // ...\r\n})\r\n\r\nconst SECOND = 1000\r\nsetTimeout(() => {\r\n  main.sendNotification()\r\n}, 3 * SECOND)\r\n```\r\n\r\nIn V2 we can do the same like this.\r\n\r\n###### Plain\r\n\r\n```jsx\r\nimport { h, app } from \"hyperapp\"\r\nimport * as Time from \"@hyperapp/time\"\r\nimport { initialState, SendNotification } from \"./app\"\r\n\r\nconst SECOND = 1000\r\n\r\napp({\r\n  init: [\r\n    state,\r\n    Time.delay({\r\n      action: SendNotification,\r\n      delay: 3 * SECOND\r\n    })\r\n  ]\r\n  // ...\r\n})\r\n```\r\n\r\n###### JSX\r\n\r\n```jsx\r\nimport { h, app } from \"hyperapp\"\r\nimport * as Time from \"@hyperapp/time\"\r\nimport { initialState, SendNotification } from \"./app\"\r\n\r\nconst SECOND = 1000\r\n\r\napp({\r\n  init: [state, <Time.delay action={SendNotification} delay={3 * SECOND} />]\r\n  // ...\r\n})\r\n```\r\n\r\nThink of the app's function `init` property as the _first_ action. After Hyperapp has loaded it will dispatch the init action to start your app and update your state with its return value, or as in the example above, update the state and execute the Time.delay effect.\r\n\r\n### HTTP requests\r\n\r\nWIP\r\n\r\n###### Plain\r\n\r\n```jsx\r\nimport * as Http from \"@hyperapp/http\"\r\nimport { Populate } from \"./actions\"\r\n\r\nconst DownloadRepos = state => [\r\n  state,\r\n  Http.fetch({\r\n    url: \"https://api.github.com/orgs/hyperapp/repos?per_page=100\",\r\n    action: Populate\r\n  })\r\n]\r\n```\r\n\r\n###### JSX\r\n\r\n```jsx\r\nimport * as Http from \"@hyperapp/http\"\r\nimport { Populate } from \"./actions\"\r\n\r\nconst DownloadRepos = state => [\r\n  state,\r\n  <Http.fetch\r\n    url={\"https://api.github.com/orgs/hyperapp/repos?per_page=100\"}\r\n    action={Populate}\r\n  />\r\n]\r\n```\r\n\r\n<a name=\"implementing-custom-effects\"></a>\r\n\r\n## Implementing custom effects\r\n\r\nWIP\r\n\r\n```jsx\r\nconst effect = ({ action, duration }, dispatch) =>\r\n  setTimeout(() => dispatch(action), duration)\r\n\r\nexport const delay = ({ action, duration }) => ({\r\n  effect,\r\n  action,\r\n  duration\r\n})\r\n```\r\n"},"comment":{"url":"https://api.github.com/repos/hyperapp/hyperapp/issues/comments/417157572","html_url":"https://github.com/hyperapp/hyperapp/issues/750#issuecomment-417157572","issue_url":"https://api.github.com/repos/hyperapp/hyperapp/issues/750","id":417157572,"node_id":"MDEyOklzc3VlQ29tbWVudDQxNzE1NzU3Mg==","user":{"login":"jorgebucaran","id":56996,"node_id":"MDQ6VXNlcjU2OTk2","avatar_url":"https://avatars1.githubusercontent.com/u/56996?v=4","gravatar_id":"","url":"https://api.github.com/users/jorgebucaran","html_url":"https://github.com/jorgebucaran","followers_url":"https://api.github.com/users/jorgebucaran/followers","following_url":"https://api.github.com/users/jorgebucaran/following{/other_user}","gists_url":"https://api.github.com/users/jorgebucaran/gists{/gist_id}","starred_url":"https://api.github.com/users/jorgebucaran/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jorgebucaran/subscriptions","organizations_url":"https://api.github.com/users/jorgebucaran/orgs","repos_url":"https://api.github.com/users/jorgebucaran/repos","events_url":"https://api.github.com/users/jorgebucaran/events{/privacy}","received_events_url":"https://api.github.com/users/jorgebucaran/received_events","type":"User","site_admin":false},"created_at":"2018-08-30T01:14:44Z","updated_at":"2018-08-30T01:14:44Z","author_association":"MEMBER","body":"@selfup Yeah, I know! Fortunately, the build configuration part of the process can be automated, the hard part is and has always been writing and keeping the documentation up to date."}}
{"actor":{"display_login":"jorgebucaran"}}