{"id":22740246,"url":"https://github.com/mtzaquia/loadablereducer","last_synced_at":"2025-04-14T05:52:13.529Z","repository":{"id":181628947,"uuid":"666977436","full_name":"mtzaquia/LoadableReducer","owner":"mtzaquia","description":"An extension to TCA's ReducerProtocol for features that will start, load additional data asynchronously, and shift to a ready state once done.","archived":false,"fork":false,"pushed_at":"2024-01-08T18:11:55.000Z","size":135,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-10T08:59:18.358Z","etag":null,"topics":["asynchronous","loading","reducer","swift-composable-architecture","tca"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mtzaquia.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2023-07-16T08:16:48.000Z","updated_at":"2024-04-04T08:51:57.000Z","dependencies_parsed_at":"2023-10-10T17:27:02.777Z","dependency_job_id":"2e369e01-791a-4532-878a-2ce6ef8a20af","html_url":"https://github.com/mtzaquia/LoadableReducer","commit_stats":null,"previous_names":["mtzaquia/loadablereducer"],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mtzaquia%2FLoadableReducer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mtzaquia%2FLoadableReducer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mtzaquia%2FLoadableReducer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mtzaquia%2FLoadableReducer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mtzaquia","download_url":"https://codeload.github.com/mtzaquia/LoadableReducer/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248830389,"owners_count":21168272,"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":["asynchronous","loading","reducer","swift-composable-architecture","tca"],"created_at":"2024-12-10T23:08:01.520Z","updated_at":"2025-04-14T05:52:13.509Z","avatar_url":"https://github.com/mtzaquia.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# LoadableReducer\n\nAn extension to TCA's `Reducer` to create a reducer that starts with initial data, loads what it needs asynchronously, and shifts to a ready state once done.\n\n## Instalation\n\n`LoadableReducer` is available via Swift Package Manager.\n\n```swift\ndependencies: [\n  .package(url: \"https://github.com/mtzaquia/LoadableReducer.git\", .upToNextMajor(from: \"1.0.0\")),\n],\n```\n\n\u003e **Note:**\n\u003e If you haven't migrated to TCA `1.0.0`, the branch `zaquia/0.9.0` uses TCA `0.55.1`.\n\n## Usage\n\nYou can use the `LoadableReducer` in 3 simple steps: reducer, view, and store.\n\n### The reducer\n\nConform your new reducer with `LoadableReducer`, and implement your TCA reducer as you normally would:\n\n```swift\nstruct MyFeature: LoadableReducer { // instead of `Reducer`\n  // ...\n}\n```\n\nThere are three required steps to conform to `LoadableReducer`:\n1. Conform your `State` to `LoadedState`. This will require you to hold on to an instance of your `LoadingState`;\n2. Provide a `LoadingState` type that includes data you will have available when the feature starts, but before it fully loads;\n3. Declare the `load(_:)` function. It receives the `LoadingState` and returns the reducer's `State`.\n\n```swift\nstruct MyFeature: LoadableReducer {\n  struct State: LoadedState { // 1. conform your state to `LoadedState`\n    var loadingState: LoadingState \n    /* ... */\n  }\n\n  enum Action { /* ... */ }\n  var body: some ReducerOf\u003cSelf\u003e { /* ... */ }\n}\n\nextension MyFeature {\n  struct LoadingState: Equatable { // 2. Declare a `LoadingState` type. \n    /* ... */\n  }\n  \n  func load(_ loadingState: LoadingState) async throws -\u003e State { // 3. Add the `load(_:)` function.\n    /* ... */\n  }\n}\n```\n\n\u003e **Note:**\n\u003e By default, your reducer does the first load when the initial view appears, but you can customise that by providing your own initial view (see [The view](#the-view)).\n\n**[Optional]** You may refresh or reload the reducer based on a \"ready\" action. Refreshing will preserve your current loaded content, but reloading will not.\n\n```swift\nstruct MyFeature: LoadableReducer {\n  /* ... */\n   \n  enum Action {\n    case somethingChanged \n    /* ... */ \n  }\n\n  func updateRequest(for action: Action) -\u003e UpdateRequest? {\n    if action == .somethingChanged {\n      return .reload // the reducer will discard the current data and will fetch new data.\n    }\n\n    return nil // does nothing.\n  }\n}\n```\n\n### The view\n\nYou can build a view for your loadable reducer with `WithLoadableStore(...)`. An optional `animation` parameter gives you control over the transition animation between states. \n\n```swift\nstruct MyFeatureView: View {\n  // 1. Declare a store to your loadable reducer using the convenience alias.\n  let store: LoadableStoreOf\u003cMyFeature\u003e\n\n  var body: some View {\n    // 2. Build your views with the `WithLoadableStore(...)` helper.\n    WithLoadableStore(store) { loadedStore in\n      WithViewStore(loadedStore) { viewStore in\n        VStack {\n          Text(\"Ready!\")\n          Button(\"Reload\") { viewStore.send(.reload) }\n        }\n      }\n    }\n  }\n}\n```\n\n**[Optional]** You may override the default loading view. When doing so, make sure to trigger the built-in `.load` action at some point, or the loading will never start.\n\n\u003e **Note:**\n\u003e The same applies for the error view and its `.retry` action.\n\n```swift\nstruct MyFeatureView: View {\n  /* ... */\n\n  var body: some View {\n    WithLoadableStore(store) { loadedStore in\n      /* ... */\n    } loading: { loadingStore in\n      WithViewStore(loadingStore) { viewStore in\n        Text(\"Loading...\")\n          .onAppear {\n            // You must explicitly call `.load` when overriding the default loading view. \n            viewStore.send(.load)\n          } \n      }\n    }\n  }\n}\n```\n\n### The store\n\nWhen creating your store, make sure to wrap your `LoadableReducer` within a `LoadingReducer`.\n\n```swift\nMyFeatureView(\n  store: .init(\n    initialState: .loading(.init(/* ... */))\n  ) { \n    LoadingReducer(reducer: MyFeature()) \n  }\n)\n```\n\n### Composing\n\nWhen composing features, refer to the `LoadableState` and `LoadableAction` of your `LoadableReducer`, and don't forget to wrap your composed reducer in a `LoadingReducer`, too.\n\n```swift\nstruct MyFeature: LoadableReducer {\n  struct State: LoadedState {\n    // Refer to the `LoadableState` instead of `State`.\n    @PresentationState var other: OtherFeature.LoadableState? \n    \n    /* ... */\n  }\n\n  enum Action {\n    // Refer to the `LoadableAction` instead of `Action`.\n    case other(PresentationAction\u003cOtherFeature.LoadableAction\u003e) \n    \n    /* ... */\n  }\n\n  var body: some ReducerOf\u003cSelf\u003e {\n    Reduce { /* ... */ }\n      .ifLet(\\.$other, action: /Action.other) {\n        // Don't forget to wrap your reducer in a `LoadingReducer`!\n        LoadingReducer(reducer: OtherFeature()) \n      }\n  }\n}\n```\n\n## Missing features\n\n- [X] Proper error handling: built-in error view and `retry` action.\n- [X] Task cancellation tweaks.\n\n## License\n\nCopyright (c) 2023 @mtzaquia\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmtzaquia%2Floadablereducer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmtzaquia%2Floadablereducer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmtzaquia%2Floadablereducer/lists"}