{"id":19749070,"url":"https://github.com/vincentbel/ts-network","last_synced_at":"2025-04-30T08:34:29.731Z","repository":{"id":57380948,"uuid":"130730050","full_name":"vincentbel/ts-network","owner":"vincentbel","description":"A TypeScript datatype representing network state which takes advantage of discriminated unions","archived":false,"fork":false,"pushed_at":"2018-04-23T17:34:57.000Z","size":298,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-04-29T06:21:30.689Z","etag":null,"topics":["discriminated-unions","network","typescript-datatype"],"latest_commit_sha":null,"homepage":"https://vincentbel.github.io/ts-network/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vincentbel.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-04-23T17:07:54.000Z","updated_at":"2023-03-10T10:11:38.000Z","dependencies_parsed_at":"2022-09-19T16:52:01.071Z","dependency_job_id":null,"html_url":"https://github.com/vincentbel/ts-network","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vincentbel%2Fts-network","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vincentbel%2Fts-network/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vincentbel%2Fts-network/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vincentbel%2Fts-network/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vincentbel","download_url":"https://codeload.github.com/vincentbel/ts-network/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224204383,"owners_count":17273111,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["discriminated-unions","network","typescript-datatype"],"created_at":"2024-11-12T02:24:52.525Z","updated_at":"2024-11-12T02:24:53.127Z","avatar_url":"https://github.com/vincentbel.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# TypeScript Network Types\n\n[![Travis](https://img.shields.io/travis/VincentBel/ts-network.svg)](https://travis-ci.org/VincentBel/ts-network)\n[![Coveralls](https://img.shields.io/coveralls/VincentBel/ts-network.svg)](https://coveralls.io/github/VincentBel/ts-network)\n[![Dev Dependencies](https://david-dm.org/VincentBel/ts-network/dev-status.svg)](https://david-dm.org/VincentBel/ts-network?type=dev)\n[![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg)](https://github.com/prettier/prettier)\n\nA TypeScript datatype representing network state which taking advantage of [discriminated unions](https://www.typescriptlang.org/docs/handbook/advanced-types.html#discriminated-unions) (or tagged unions, algebraic data types).\n\n## The Problem\n\nWhen representing network state, the following data structure is commonly used:\n\n```js\nvar state = {\n  isFetching: true, // whether we are fetching the data\n  data: [],         // the data we fetched\n  error: {},        // failed to fetch the data, but got an error\n}\n```\n\nWe may raise the following questions:\n\n* What does it mean if `isFetching === true` and `error` field also exists? Refetching after failed? Can it mean something else?\n* What does it mean if all of the three fields exist?\n* When rendering the UI, which field should I read first?\n* How can I distinguish between initial page(not requested) and empty page(response with empty data)? by checking `data === null` or `data.length === 0`?\n\nAs we can see, it is **hard to reason about** by using this data structure, and it exists some **impossible state**.\n\n## A Solution\n\nThe above data structure is not a good model of the network state. Actually, network states are consist of:\n\n* haven't start the request(`NotRequested`)\n* the request started, and haven't get the response yet(`Requesting`)\n* the request succeeded, responded with some data(`Succeeded`)\n* the request failed, responded with error(`Failed`)\n\nAnd in some cases, we can refresh the data:\n\n* refreshing by restart the request after succeeded(`Refreshing`)\n* refresh succeeded, got the new data(`RefreshSucceeded`)\n* refresh failed, got an error(`RefreshFailed`)\n\nThis is how this library trying to solve the problem. See the solution at the following Usage section.\n\n## Usage\n\n```ts\nimport {\n  NetworkState,\n  getNotRequested,\n  getRequesting,\n  getSucceeded,\n  getFailed,\n} from 'ts-network'\n\ntype User = {\n  id: number\n  name: string\n  email: string\n}\n\ntype NetworkError = {\n  statusCode: number\n  message: number\n}\n\ntype UserListRequestState = NetworkState\u003cUser[], NetworkError\u003e\n\n// How to set the state\nfunction getUserListRequestState(action: Action) {\n  switch (action.type) {\n    case 'user-list-request-started':\n      return getRequesting()\n    case 'user-list-request-succeeded':\n      return getSucceeded(action.response)\n    case 'user-list-request-failed':\n      return getFailed(action.error)\n    default:\n      return getNotRequested()\n  }\n}\n\n// How to use the state to render UI\nfunction render(userListRequest: UserListRequestState): UIElement {\n  switch (userListRequest.kind) {\n    case 'not-requested':\n    // render initial page\n    case 'requesting':\n    // render loading page\n    case 'succeeded':\n    // render user list by using `userListRequest.data`\n    case 'failed':\n    // render error message by using `userListRequest.error`\n\n    // TypeScript will raise an error if you misspell the case (like `case 'fialed':`).\n    // You can skip the `default` case if you have already checked all the kinds.\n    // And if you only check some kinds and without the `default` case, TypeScript\n    // will raise an error. (You should turn on `strictNullChecks` first)\n  }\n}\n```\n\n## API\n\nSee \u003chttps://vincentbel.github.io/ts-network\u003e.\n\n## FAQ\n\n**Why are there no `RefreshSucceeded` state?**\n\nBecause in common use case, after refreshing succeeded, we will rerender the UI with the new data we fetched, and turning the UI to `Succeeded` state waiting for another refreshing. So, using `Succeeded` state is enough.\n\nIf in your special case you need to store the previous data after refreshing succeeded, you should construct your own network state. like\n\n```ts\ninterface RefreshSucceeded\u003cD\u003e {\n  kind: 'refresh-succeed'\n  prevData: D\n  data: D\n}\n\ntype MySpecialNetworkState\u003cD, E\u003e =\n  | NotRequested\n  // ...\n  | RefreshingSucceeded\u003cD\u003e\n```\n\n## Thanks\n\nThis library is largely inspired by the post [How Elm Slays a UI Antipattern](http://blog.jenkster.com/2016/06/how-elm-slays-a-ui-antipattern.html). And the talk [Making Impossible States Impossible](https://www.youtube.com/watch?v=IcgmSRJHu_8) is great to watch.\n\n## License\n\n[MIT](./LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvincentbel%2Fts-network","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvincentbel%2Fts-network","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvincentbel%2Fts-network/lists"}