{"id":13694606,"url":"https://github.com/pitt500/OnlineStoreTCA","last_synced_at":"2025-05-03T04:30:42.032Z","repository":{"id":53267926,"uuid":"495611635","full_name":"pitt500/OnlineStoreTCA","owner":"pitt500","description":"A demo covering the basics of the Composable Architecture (TCA)","archived":false,"fork":false,"pushed_at":"2024-11-06T14:43:45.000Z","size":9166,"stargazers_count":210,"open_issues_count":0,"forks_count":29,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-11-12T21:38:59.059Z","etag":null,"topics":[],"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/pitt500.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,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-05-24T00:07:15.000Z","updated_at":"2024-11-12T14:40:37.000Z","dependencies_parsed_at":"2024-04-12T23:45:24.688Z","dependency_job_id":"679d9b49-f7da-426c-b328-14a10fb997c4","html_url":"https://github.com/pitt500/OnlineStoreTCA","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pitt500%2FOnlineStoreTCA","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pitt500%2FOnlineStoreTCA/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pitt500%2FOnlineStoreTCA/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pitt500%2FOnlineStoreTCA/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pitt500","download_url":"https://codeload.github.com/pitt500/OnlineStoreTCA/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252144318,"owners_count":21701387,"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":[],"created_at":"2024-08-02T17:01:35.590Z","updated_at":"2025-05-03T04:30:37.269Z","avatar_url":"https://github.com/pitt500.png","language":"Swift","readme":"# Before starting\n- This demo was implemented using version [1.15.2](https://pointfreeco.github.io/swift-composable-architecture/1.14.0/documentation/composablearchitecture/) of TCA.\n- The demo runs on iOS 17.6 and above.\n- All credits about TCA go to [Brandon Willams](https://twitter.com/mbrandonw), [Stephen Celis](https://twitter.com/stephencelis) and the incredible team at [pointfree.co](https://www.pointfree.co/) ❤️.\n\n# Online Store made with Composable Architecture (TCA)\nThe purpose of this demo is to provide an introduction to the main concepts of TCA. If you are new to TCA, I **highly** recommend starting with the README from the [main repository](https://github.com/pointfreeco/swift-composable-architecture) and watching the informative [Tour of TCA](https://www.pointfree.co/collections/composable-architecture/a-tour-of-the-composable-architecture). These resources will provide you with a solid foundation and a comprehensive understanding of the TCA framework.\n\n## Content\n* [Motivation](#motivation)\n* [Screenshots of the app](#screenshots)\n* [The basics](#the-basics)\n    * [Archiecture Diagram](#archiecture-diagram)\n    * [Hello World Example](#hello-world-example)\n* [Composition](#composition)\n    * [Body to compose multiple Reducers](#body-to-compose-multiple-reducers)\n    * [Single state operators](#single-state-operators)\n      * [store.scope(state:action:)](#storescopestateaction)\n      * [Scope in Reducers](#scope-in-reducers)\n    * [Collection of states](#collection-of-states)\n      * [forEach in Reducer](#foreach-in-reducer)\n* [Dependencies](#dependencies)\n* [Side Effects](#side-effects)\n    * [Network Calls](#network-calls)\n* [Navigation](#navigation)\n    * [Alerts](#alerts)\n    * [Sheets](#sheets)\n* [Testing](#testing)\n    * [Basics](#testing-basics)\n    * [Side Effects](#testing-side-effects)\n    * [CasePathable](#testing-CasePathable)\n* [Other Topics](#other-topics)\n    * [Optional States](#optional-states)\n    * [Private Actions](#private-actions)\n    * [Making a Root Domain with Tab View](#making-a-root-domain-with-tab-view)\n* [Contact](#contact)\n\n\n## Motivation\n**TL;DR:** This project aims to build an app using TCA, striking a balance between simplicity and complexity. It focuses on exploring the most important use cases of TCA while providing concise and accessible documentation for new learners. The goal is to create a valuable learning resource that offers practical insights into using TCA effectively.\n\nI aimed to showcase the power of the TCA architecture in building robust applications for the Apple ecosystem, including iOS, macOS, and more excitingly, its future expansion beyond the Apple world! 🚀\n\nWhile there are many articles available that demonstrate simple one-screen applications to introduce TCA's core concepts, I noticed a gap between these basic demos and real-world applications like [isoword](https://github.com/pointfreeco/isowords), which can be complex and challenging to understand certain important use cases (like navigation and how reducers are glued).\n\nIn this demo, I have implemented a minimal online store that connects to a real network API (https://fakestoreapi.com). It features a product list, the ability to add items to the cart, and the functionality to place orders. While the requests are not processed in real-time (as it uses a fake API), the network status is simulated, allowing you to experience the interaction and mapping of network calls using TCA.\n\nWhile this demo may not be a full-scale real-world application, it includes enough reducers to illustrate how data can be effectively connected and how domains can be isolated to handle specific components within the app (e.g., Tabs -\u003e Product List -\u003e Product Cell -\u003e Add to Cart button).\n\nFurthermore, I have created tests to demonstrate one of TCA's key features: ensuring that tests fail if the expected state mutations are not captured accurately. This showcases how TCA promotes testability and helps ensure the correctness of your application.\n\nIf you're looking to dive into TCA, this demo provides a valuable middle ground between simple examples and complex projects, offering concise documentation and practical insights into working with TCA in a more realistic application setting.\n\nAny feedback is welcome! 🙌🏻\n\n## Screenshots\n### Tabs\n\u003cimg src=\"./Images/demo1.png\"  width=\"25%\" height=\"25%\"\u003e|\u003cimg src=\"./Images/demo2.png\"  width=\"25%\" height=\"25%\"\u003e|\u003cimg src=\"./Images/demo6.png\"  width=\"25%\" height=\"25%\"\u003e\n\n### Cart\n\u003cimg src=\"./Images/demo3.png\"  width=\"25%\" height=\"25%\"\u003e|\u003cimg src=\"./Images/demo4.png\"  width=\"25%\" height=\"25%\"\u003e|\u003cimg src=\"./Images/demo5.png\"  width=\"25%\" height=\"25%\"\u003e\n\n## The basics\n### Archiecture Diagram\n\u003cimg src=\"./Images/TCA_Architecture2.png\"\u003e\n\n### Hello World Example\nConsider the following implementation of a simple app using TCA, where you will have two buttons: one to increment a counter displayed on the screen and the other to decrement it.\n\nHere's an example of how this app would be coded with TCA:\n\n1. A struct that will represent the domain of the feature. This struct must conform `ReducerProtocol` protocol and providing `State` struct, `Action` enum and `reduce` method.\n\n```swift\nstruct CounterDomain: ReducerProtocol {\n    struct State {\n        // State of the feature\n    }\n\n    enum Action {\n        // actions that use can do in the app\n    }\n    \n    func reduce(into state: inout State, action: Action) -\u003e EffectTask\u003cAction\u003e {\n        // Method that will mutate the state given an action.\n    }\n}\n```\n\n2. The view that is presented in the screen will display the current state of the app.\n\u003cimg src=\"./Images/viewDemo1.png\" width=\"30%\" height=\"30%\"\u003e\n\n```swift\nstruct State: Equatable {\n    var counter = 0\n}\n```\n\n3. When the user presses a button (let's say increase button), it will internally send an action to the store.\n\u003cimg src=\"./Images/actionDemo1.png\" width=\"30%\" height=\"30%\"\u003e\n\n```swift\nenum Action: Equatable {\n    case increaseCounter\n    case decreaseCounter\n}\n```\n\n4. The action will be received by the reducer and proceed to mutate the state. Reducer MUST also return an effect, that represent logic from the \"outside world\" (network calls, notifications, database, etc). If no effect is needed, just return `EffectTask.none` .\n\n```swift\nfunc reduce(into state: inout State, action: Action) -\u003e EffectTask\u003cAction\u003e {\n    switch action {\n    case .increaseCounter:\n        state.counter += 1\n        return .none\n    case .decreaseCounter:\n        state.counter -= 1\n        return .none\n    }\n}\n```\n\n5. Once the mutation is done and the reducer returned the effect, the view will render the update in the screen. \n\u003cimg src=\"./Images/viewUpdateDemo1.png\" width=\"30%\" height=\"30%\"\u003e\n\n6. To observe state changes in TCA, we need an object called `viewStore`, that in this example is wrapped within WithViewStore view. We can send an action from the view to the store using `viewStore.send()` and an `Action` value.\n\n```swift\nstruct ContentView: View {\n    let store: Store\u003cState, Action\u003e\n\n    var body: some View {\n        WithViewStore(self.store) { viewStore in\n            HStack {\n                Button {\n                    viewStore.send(.decreaseCounter)\n                } label: {\n                    Text(\"-\")\n                        .padding(10)\n                        .background(.blue)\n                        .foregroundColor(.white)\n                        .cornerRadius(10)\n                }\n                .buttonStyle(.plain)\n\n                Text(viewStore.counter.description)\n                    .padding(5)\n\n                Button {\n                    viewStore.send(.increaseCounter)\n                } label: {\n                    Text(\"+\")\n                        .padding(10)\n                        .background(.blue)\n                        .foregroundColor(.white)\n                        .cornerRadius(10)\n                }\n                .buttonStyle(.plain)\n            }\n        }\n    }\n}\n```\n\n7. View is initialized by a `Store` object.\n\n```swift\nContentView(\n    store: Store(\n        initialState: CounterDomain.State(),\n        reducer: CounterDomain()\n    )\n)\n```\n\nIf you want to learn more about the basics, check out the following [video](https://youtu.be/SfFDj6qT-xg)\n\n\u003e Note: The videos shared here were made using the legacy version of TCA with Environment and without `ReducerProtocol`. If you want to see the legacy version of TCA, check out this [branch](https://github.com/pitt500/OnlineStoreTCA/tree/legacy-tca-with-environment).\n\n## Composition\n\nComposition refers to the process of building complex software systems by combining smaller, reusable software components. Take a look to this image:\n\n\u003cimg src=\"./Images/composition2.png\" width=\"80%\" height=\"80%\"\u003e\n\nWe started with a simple button counter, then we add an extra state to display text, next we put the whole button in a Product cell, and finally, each product cell will be part of a Product list. That is composition!\n\n### Body to compose multiple Reducers\nIn the previous example, we demonstrated the usage of `reduce(into:action:)` to create our reducer function and define how state will be modified for each action. However, it's important to note that this method is suitable only for leaf components, which refer to the smallest components in your application.\n\nFor larger components, we can leverage the `body` property provided by the `ReducerProtocol`. This property enables you to combine multiple reducers, facilitating the creation of more comprehensive components. By utilizing the `body` property, you can effectively compose and manage the state mutations of these larger components.\n```swift\nvar body: some ReducerProtocol\u003cState, Action\u003e {\n    ChildReducer1()\n    Reduce { state, action in\n        switch action {\n        case .increaseCounter:\n            state.counter += 1\n            return .none\n        case .decreaseCounter:\n            state.counter -= 1\n            return .none\n        }\n    }\n    ChildReducer2()\n}\n```\n\nThe `Reduce` closure will always encapsulate the logic from the parent domain. To understand how to combine additional components, please continue reading below.\n\n\u003e Compared to the previous version of TCA without `ReducerProtocol`, the order of child reducers will not affect the result. Parent Reducer (`Reduce`) will be always executed at the end.\n\n### Single state operators\n\nFor single states (all except collections/lists), TCA provides operators to glue the components and make bigger ones.\n\n#### store.scope(state:action:) \n`store.scope` is an operator used in views to get the child domain's (`AddToCartDomain`) state and action from parent domain (`ProductDomain`) to initialize subviews. \nFor example, the `ProductDomain` below contains two properties as part of its state: `product` and `addToCartState`.\n\n```swift\nstruct ProductDomain: ReducerProtocol {\n    struct State: Equatable, Identifiable {\n        let product: Product\n        var addToCartState = AddToCartDomain.State()\n    }\n    // ...\n```\n\nFurthermore, we utilize an action with an associated value that encapsulates all actions from the child domain, providing a comprehensive and cohesive approach.\n```swift\nstruct ProductDomain: ReducerProtocol {\n    // State ...\n\n    enum Action {\n        case addToCart(AddToCartDomain.Action)\n    }\n    // ...\n```\n\nLet's consider the scenario where we need to configure the `ProductCell` view below. The `ProductCell` is designed to handle the `ProductDomain`, while we need to provide some information to initialize the `AddToCartButton`. However, the `AddToCartButton` is only aware of its own domain, `AddToCartDomain`, and not the `ProductDomain`. To address this, we can use the `scope` method from `store` to get the child's state and action from parent domain. This enables us to narrow down the scope of the button to focus solely on its own functionality.\n\n```swift\nstruct ProductCell: View {\n    let store: Store\u003cProductDomain.State, ProductDomain.Action\u003e\n    \n    var body: some View {\n        WithViewStore(self.store) { viewStore in\n            // More views here ...\n            AddToCartButton(\n                store: self.store.scope(\n                    state: \\.addToCartState,\n                    action: ProductDomain.Action.addToCart\n                )\n            )\n        }\n    }\n```\nBy employing this approach, the `AddToCartDomain` will solely possess knowledge of its own state and remain unaware of any product-related information.\n\n#### Scope in Reducers\n`Scope` is utilized within the `body` to seamlessly transform the child reducer (`AddToCart`) into a compatible form that aligns with the parent reducer (`Product`). This allows for smooth integration and interaction between the two.\n```swift\nvar body: some ReducerProtocol\u003cState, Action\u003e {\n    Scope(state: \\.addToCartState, action: /ProductDomain.Action.addToCart) {\n        AddToCartDomain()\n    }\n    Reduce { state, action in\n        // Parent Reducer logic ...\n    }\n}\n```\nThis transformation becomes highly valuable when combining multiple reducers to construct a more complex component.\n\n\u003e In earlier versions, the `pullback` and `combine` operators were employed to carry out the same operation. You can watch this [video](https://youtu.be/Zf2pFEa3uew).\n\n### Collection of states\n\nAre you looking to manage a collection of states? TCA offers excellent support for that as well!\n\nIn this particular example, instead of using a regular array, TCA requires a list of (`Product`) states, which can be achieved by utilizing `IdentifiedArray`:\n```swift\nstruct ProductListDomain: ReducerProtocol {\n    struct State: Equatable {\n        var productList: IdentifiedArrayOf\u003cProductDomain.State\u003e = []\n        // ...    \n    }\n    // ...\n}\n```\n\n#### forEach in Reducer\n\nThe `forEach` operator functions similarly to the [`Scope`](#scope-in-reducers) operator, with the distinction that it operates on a collection of states. It effectively transforms the child reducers into compatible forms that align with the parent reducer.\n\n```swift\nstruct ProductListDomain: ReducerProtocol {\n    // State and Actions ...\n    \n    var body: some ReducerProtocol\u003cState, Action\u003e {\n        Reduce { state, action in\n            // Parent Reducer...\n        }\n        .forEach(\n            \\.productList, \n            action: /ProductListDomain.Action.product(id:action:)\n        ) {\n            ProductDomain()\n        }\n    }\n}\n```\n\nSubsequently, in the user interface, we employ `ForEachStore` and `store.scope` to iterate through all the (`Product`) states and actions. This enables us to send actions to the corresponding cell and modify its state accordingly.\n```swift\nList {\n    ForEachStore(\n        self.store.scope(\n            state: \\.productListState,\n            action: ProductListDomain.Action\n                .product(id: action:)\n        )\n    ) {\n        ProductCell(store: $0)\n    }\n}\n```\n\n\u003e There's a legacy `forEach` operator, If you want to learn more, check out this [video](https://youtu.be/sid-zfggYhQ)\n\n## Dependencies\nIn previous iterations of TCA, `Environment` played a crucial role in consolidating all the dependencies utilized by a domain.\n\nWith the introduction of the [`ReducerProtocol`](https://www.pointfree.co/blog/posts/81-announcing-the-reducer-protocol), we have eliminated the concept of `Environment`. As a result, dependencies now reside directly within the domain.\n\n```swift\nstruct ProductListDomain: ReducerProtocol {\n    // State ...\n\n    // Actions...\n\n    var fetchProducts:  () async throws -\u003e [Product]\n    var sendOrder: ([CartItem]) async throws -\u003e String\n    var uuid: () -\u003e UUID\n\n    // Reducer ...\n}\n```\n\nNevertheless, we have the option to leverage the [Dependencies Framework](https://github.com/pointfreeco/swift-dependencies) to achieve a more enhanced approach in managing our dependencies:\n\n```swift\nstruct ProductListDomain: ReducerProtocol {\n    // State ...\n\n    // Actions...\n\n    @Dependency(\\.apiClient.fetchProducts) var fetchProducts\n    @Dependency(\\.apiClient.sendOrder) var sendOrder\n    @Dependency(\\.uuid) var uuid\n\n    // Reducer ...\n}\n```\n\n\u003e If you want to learn more about how Environment object works on TCA, take a look to this [video](https://youtu.be/sid-zfggYhQ?list=PLHWvYoDHvsOVo4tklgLW1g7gy4Kmk4kjw\u0026t=103)\n\n## Side Effects\nA side effect refers to an observable change that arises when executing a function or method. This encompasses actions such as modifying state outside the function, performing I/O operations to a file or making network requests. TCA facilitates the encapsulation of such side effects through the use of `EffectTask` objects.\n\n\u003cimg src=\"./Images/sideEffects1.png\" width=\"80%\" height=\"80%\"\u003e\n\n\u003e If you want to learn more about side effects, check out this [video](https://youtu.be/t3HHam3GYkU)\n\n### Network calls\nNetwork calls are a fundamental aspect of mobile development, and TCA offers robust tools to handle them efficiently. As network calls are considered external interactions or [side effects](#side-effects), TCA utilizes the `EffectTask` object to encapsulate these calls. Specifically, network calls are encapsulated within the `EffectTask.task` construct, allowing for streamlined management of asynchronous operations within the TCA framework.\n\nHowever, it's important to note that the task operator alone is responsible for making the web API call. To obtain the actual response, an additional action needs to be implemented, which will capture and store the result within a `TaskResult` object.\n\n```swift\nstruct ProductListDomain: ReducerProtocol {\n    // State and more ...\n    \n    enum Action: Equatable {\n        case fetchProducts\n        case fetchProductsResponse(TaskResult\u003c[Product]\u003e)\n    }\n   \n    var fetchProducts: () async throws -\u003e [Product]\n    var uuid: () -\u003e UUID\n    \n    var body: some ReducerProtocol\u003cState, Action\u003e {\n        // Other child reducers...\n        Reduce { state, action in\n            switch action {\n            case .fetchProducts:\n                return .task {\n                    // Just making the call \n                    await .fetchProductsResponse(\n                        TaskResult { try await fetchProducts() }\n                    )\n                }\n            case .fetchProductsResponse(.success(let products)):\n                // Getting the success response\n                state.productListState = IdentifiedArrayOf(\n                    uniqueElements: products.map {\n                        ProductDomain.State(\n                            id: uuid(),\n                            product: $0\n                        )\n                    }\n                )\n                return .none\n            case .fetchProductsResponse(.failure(let error)):\n                // Getting an error from the web API\n                print(\"Error getting products, try again later.\", error)\n                return .none\n            }\n        }\n    }\n}\n```\n\n\u003e To learn more about network requests in TCA, I recommend watching this insightful [video](https://youtu.be/sid-zfggYhQ?list=PLHWvYoDHvsOVo4tklgLW1g7gy4Kmk4kjw\u0026t=144) that explains asynchronous requests. Additionally, you can refer to this informative [video](https://youtu.be/j2qymM6i9n4) that demonstrates the configuration of a real web API call, providing practical insights into the process.\n\n## Navigation\n\n Navigation is a huge and complex topic. Navigation are alerts, confirmation dialogs, sheets, popovers and links. Also, you can add a custom navigations if you want. In this project you will see alerts and sheets.\n\n### Alerts\n\nThe TCA library offers support for `AlertView`, enabling the addition of custom state and a consistent UI building approach without deviating from the TCA architecture. To create your own alert using TCA, follow these steps:\n\n1. Create the alert actions inside of the Action enum of the reducer. The recommended way is create a nested enum inside the action.\n\n```swift\nenum Action: Equatable {\n    enum Alert {\n        case alertAction1\n        case alertAction2\n        ....\n    }\n}\n```\n\n2. Next, create a case alert and use `PresentationAction`.\n\n```swift\nenum Action: Equatable {\n    case alert(PresentationAction\u003cAlert\u003e)\n    case alertButtonTapped\n\n    enum Alert {\n        case alertAction1\n        case alertAction2\n        ....\n    }\n}\n```\n\n`PresentationAction` is a generic that represents the presented actions and an special action named dismiss. This is very useful case because with the dismiss action, the reducer can manage if a side effect is running and remove to the system. More information about effect cancelling in navigations [here](https://www.pointfree.co/collections/composable-architecture/navigation/ep225-composable-navigation-behavior).\n\n```swift\npublic enum PresentationAction\u003cAction\u003e {\n  /// An action sent to `nil` out the associated presentation state.\n  case dismiss\n\n  /// An action sent to the associated, non-`nil` presentation state.\n  indirect case presented(Action)\n}\n```\n\n3. Create an alert state inside of the reducer.\n```swift\n@Presents var alert: AlertState\u003cAction.Alert\u003e?\n```\n`@Presents` is a property wrapper that you need to use when creates a navigation state in the reducer. The reason to use `@Presents` is when composing a lots of features together, the root state could overflow the stack. More information [here](https://www.pointfree.co/collections/composable-architecture/navigation/ep230-composable-navigation-stack-vs-heap).\n\n\n4. Extent `AlertState` and create as many alerts as you want. You can create a property wrapper or a function if you need some dynamic information.\n\n```swift\nextension AlertState where Action == CartListDomain.Action.Alert {\n    static var successAlert: AlertState {\n        AlertState {\n            TextState(\"Thank you!\")\n        } actions: {\n            ButtonState(action: .dismissSuccessAlert, label: { TextState(\"Done\") })\n            ButtonState(role: .cancel, action: .didCancelConfirmation, label: { TextState(\"Cancel\") })\n        } message: {\n            TextState(\"Your order is in process.\")\n        }\n    }\n\n    static func confirmationAlert(totalPriceString: String) -\u003e AlertState {\n        AlertState {\n            TextState(\"Confirm your purchase\")\n        } actions: {\n            ButtonState(action: .didConfirmPurchase, label: { TextState(\"Pay \\(totalPriceString)\") })\n            ButtonState(role: .cancel, action: .didCancelConfirmation, label: { TextState(\"Cancel\") })\n        } message: {\n            TextState(\"Do you want to proceed with your purchase of \\(totalPriceString)?\")\n        }\n    }\n}\n```\n\n5. Inside of the body of the reducer you can set the alert. As the state is an optional value, you need to implement `ifLet` in the reducer. This is a particular modifier that not need a reducer like a tipical `ifLet` reducer. \n\nAnother question is when you use a reducer for navigation, you will use the binding operator `$` in the state. This is because navigation modifiers in SwiftUI use a binding for presenting, usually the `isPresented` boolean. In this case, in order to manage when the alert is presented or no, you use a binding state in the reducer. Now, the reducer is fully synchronized with the view.\n\n```swift\nvar body: some ReducerOf\u003cSelf\u003e {\n        Reduce { state, action in\n            switch action {\n                case .alert:\n                    return .none\n                case .alertButtonTapped:\n                    state.alert = .successAlert\n                    return .none\n            }\n        }\n        .ifLet(\n            \\.$alert, \n            action: \\.alert\n        )\n}\n```\n\n6. Finally, implements `AlertView` modifier in the view. Remember, you use the binding `$` operator.\n\n```swift\n.alert(\n\tstore: store.scope(\n\t\tstate: \\.$alert,\n\t\taction: \\.alert\n\t)\n)\n```\n\n\n\u003cdetails\u003e\n\u003csummary\u003eSee Alerts in previous versions of TCA\u003c/summary\u003e\n\nThe TCA library also offers support for `AlertView`, enabling the addition of custom state and a consistent UI building approach without deviating from the TCA architecture. To create your own alert using TCA, follow these steps:\n\n1. Create an `AlertState` with actions of your own domain.\n2. Create the actions that will trigger events for the alert:\n    - Initialize AlertState (`didPressPayButton`)\n    - Dismiss the alert (`didCancelConfirmation`)\n    - Execute the alert's handler (`didConfirmPurchase`)\n\n```swift\nstruct CartListDomain: ReducerProtocol {\n    struct State: Equatable {\n        var confirmationAlert: AlertState\u003cCartListDomain.Action\u003e?\n        \n        // More properties ...\n    }\n    \n    enum Action: Equatable {\n        case didPressPayButton\n        case didCancelConfirmation\n        case didConfirmPurchase\n        \n        // More actions ...\n    }\n    \n    var body: some ReducerProtocol\u003cState, Action\u003e {\n        Reduce { state, action in\n            switch action {\n            case .didCancelConfirmation:\n                state.confirmationAlert = nil\n                return .none\n            case .didConfirmPurchase:\n                // Sent order and Pay ...\n            case .didPressPayButton:\n                state.confirmationAlert = AlertState(\n                    title: TextState(\"Confirm your purchase\"),\n                    message: TextState(\"Do you want to proceed with your purchase of \\(state.totalPriceString)?\"),\n                    buttons: [\n                        .default(\n                            TextState(\"Pay \\(state.totalPriceString)\"),\n                            action: .send(.didConfirmPurchase)),\n                        .cancel(TextState(\"Cancel\"), action: .send(.didCancelConfirmation))\n                    ]\n                )\n                return .none\n            // More actions ...\n            }\n        }\n        .forEach(\\.cartItems, action: /Action.cartItem(id:action:)) {\n            CartItemDomain()\n        }\n    }\n}              \n```\n\u003c/details\u003e\n\n### Sheets\n\nOther type of navigation are sheets. To create your own alert using TCA, follow these steps:\n\n1. As the alerts, create the state. You use `@Presents` to avoid accidentally overflow the stack.\n\n```swift\n@Presents var cartState: CartListDomain.State?\n```\n\n2. Next, create the action. Remember to use PresentationAction inside the case of the sheet.\n\n```swift\ncase cart(PresentationAction\u003cCartListDomain.Action\u003e)\n```\n\n3. Create the `ifLet` in the reducer. Here, you need to define the reducer of the destination.\n\n```swift\n.ifLet(\\.$cartState, action: \\.cart) {\n    CartListDomain()\n}\n```\n\n4. Finally, in the view, you can define the sheet operator like this.\n\n```swift\n.sheet(\n\titem: $store.scope(\n\t\tstate: \\.cartState,\n\t\taction: \\.cart\n\t)\n) { store in\n\tCartListView(store: store)\n}\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eSee Sheets in previous versions of TCA\u003c/summary\u003e\n### Opening Modal Views\n\nIf you require to open a view modally in SwiftUI, you will need to use sheet modifier and provide a binding parameter:\n```swift\nfunc sheet\u003cContent\u003e(\n    isPresented: Binding\u003cBool\u003e,\n    onDismiss: (() -\u003e Void)? = nil, @ViewBuilder content: @escaping () -\u003e Content\n) -\u003e some View where Content : View\n```\n\nTo utilize this modifier (or any modifier with binding parameters) in TCA, it is necessary to employ the `binding` operator from `viewStore` and supply two parameters:\n\n1. The state property that will undergo mutation.\n2. The action that will trigger the mutation.\n\n```swift\n// Domain:\nstruct Domain: ReducerProtocol {\n    struct State {\n        var shouldOpenModal = false\n    }\n    enum Action {\n        case setCartView(isPresented: Bool)\n    }\n\n    var body: some ReducerProtocol\u003cState, Action\u003e {\n        Reduce { state, action in\n            switch action {\n                case .setCartView(let isPresented):\n                    state.shouldOpenModal = isPresented\n            }\n        }\n    }\n}\n\n// UI:\nText(\"Parent View\")\n.sheet(\n    isPresented: viewStore.binding(\n        get: \\.shouldOpenModal,\n        send: Action.setModalView(isPresented:)\n    )\n) {\n    Text(\"I'm a Modal View!\")\n}\n```\n\n\u003e If you want to lean more about Binding with TCA and SwiftUI, take a look to this [video](https://youtu.be/Ilr8AsoggIY).\n\u003c/details\u003e\n\n## Testing\n\n### Testing Basics\n\nTesting is a crucial part of software development. TCA has its own tools to test reducers in a very simple way.\n\nWhen you test a reducer, you will use a TestStore class passing an initial state and a reducer like the store that you are using in the production code.\n\nNext, you can send an action but, in this case, send receive a closure that you need to expect the result of this action. For example, when you send increseCounter action, you expect that count is equal to 1 if previously, your state counter is 0.\n\nFinally, you send a decreaseCounter and the expectation of this action is count state equal to 0 because previously count was setted to 1.\n\n```swift\n@MainActor\nclass CounterDomainTest: XCTestCase {\n    func testHappyPath() {\n        let store = TestStore(\n            initialState: CounterDomain.State(),\n            reducer: { CounterDomain() }\n        )\n\n        await store.send(.increaseCounter) {\n            $0.count = 1\n        }\n\n        await store.send(.decreaseCounter) {\n            $0.count = 0\n        }\n    }\n}\n```\n\n### Testing Side effects\n\nThe first thing is the ability to mock every side effect of the system. To do that TestStore has a closure for this purpose.\n\nNotice that `fetchProducts` action has a side effect. When it finishes, send an action `fetchProductsResponse` back to the system. When you test this, you will use `store.receive` for response actions.\n\n```swift\n@MainActor\nclass ProductListDomainTest: XCTestCase {\n    func testSideEffects() {\n        let products: [Product] = ...\n        let store = TestStore(\n            initialState: ProductListDomain.State(),\n            reducer: { ProductListDomain() }\n        ) {\n            $0.apiClient.fetchProducts = { products }\n        }\n\n         await store.send(.fetchProducts) {\n            $0.dataLoadingStatus = .loading\n        }\n        \n        await store.receive(.fetchProductsResponse(.success(products))) {\n            $0.products = products\n            $0.dataLoadingStatus = .success\n        }\n    }\n}\n```\n\n### Testing CasePathable\n\nCasePathable is a nice macro that it has a lot of useful tips. One of those is using keypaths for testing actions. For example, if you have this test.\n\n```swift\nawait store.send(\n            .cartItem(\n                .element(\n                    id: cartItemId1,\n                    action: .deleteCartItem(product: Product.sample[0]))\n            )\n        ) {\n            ...\n        }\n```\n\nWe can update this with:\n\n```swift\nawait store.send(\\.cartItem[id: cartItemId1].deleteCartItem, Product.sample[0]) {\n    ...\n}\n```\n\nAnother example:\n\n```swift\nawait store.send(.alert(.presented(.didConfirmPurchase)))\n```\n\n```swift\nawait store.send(\\.alert.didConfirmPurchase)\n```\n\n## Other topics\n\n### Optional States\n\nBy default, TCA keeps a state in memory throughout the entire lifecycle of an app. However, in certain scenarios, maintaining a state can be resource-intensive and unnecessary. One such case is when dealing with modal views that are displayed for a short duration. In these situations, it is more efficient to use optional states.\n\nCreating an optional state in TCA follows the same approach as declaring any optional value in Swift. Simply define the property within the parent state, but instead of assigning a default value, declare it as optional. For instance, in the provided example, the `cartState` property holds an optional state for a Cart List.\n\n```swift\nstruct ProductListDomain: ReducerProtocol {\n    struct State: Equatable {\n        var productListState: IdentifiedArrayOf\u003cProductDomain.State\u003e = []\n        var shouldOpenCart = false\n        var cartState: CartListDomain.State?\n        \n        // More properties...\n    }\n}\n```\n\nNow, in the `Reduce` function, we can utilize the `ifLet` operator to transform the child reducer (`CartListDomain`) into one that is compatible with the parent reducer (`ProductList`). \n\nIn the provided example, the `CartListDomain` will be evaluated only if the `cartState` is non-nil. To assign a new non-optional state, the parent reducer will need to initialize the property (`cartState`) when a specific action (`setCartView`) is triggered. \n\nThis approach ensures that the optional state is properly handled within the TCA framework and allows for seamless state management between the parent and the optional child reducers.\n\n```swift\nstruct ProductListDomain: ReducerProtocol {\n    // State and Actions ...\n    \n    var body: some ReducerProtocol\u003cState, Action\u003e {\n        Reduce { state, action in\n            switch action {\n            //  More cases ...\n            case .setCartView(let isPresented):\n                state.shouldOpenCart = isPresented\n                state.cartState = isPresented\n                ? CartListDomain.State(...)\n                : nil\n                return .none\n            }\n        }\n        .ifLet(\\.cartState, action: /ProductListDomain.Action.cart) {\n            CartListDomain()\n        }\n    }\n}\n```\n\nLastly, in the view, you can employ `IfLetStore` to unwrap a store with optional state. This allows you to conditionally display the corresponding view that operates with that particular state.\n\n\n```swift\nList {\n    ForEachStore(\n        self.store.scope(\n            state: \\.productListState,\n            action: ProductListDomain.Action\n                .product(id: action:)\n        )\n    ) {\n        ProductCell(store: $0)\n    }\n}\n.sheet(\n    isPresented: viewStore.binding(\n        get: \\.shouldOpenCart,\n        send: ProductListDomain.Action.setCartView(isPresented:)\n    )\n) {\n    IfLetStore(\n        self.store.scope(\n            state: \\.cartState,\n            action: ProductListDomain.Action.cart\n        )\n    ) {\n        CartListView(store: $0)\n    }\n}\n```\n\n\u003e If you want to learn more about optional states, check out this [video](https://youtu.be/AV0laQw2OjM).\n\n### Private Actions\n\nBy default, when you declare an action in a TCA domain, it is accessible to other reducers as well. However, there are situations where an action is intended to be specific to a particular reducer and does not need to be exposed outside of it. \n\nIn such cases, you can simply declare private functions to encapsulate those actions within the domain's scope. This approach ensures that the actions remain private and only accessible within the intended context, enhancing the encapsulation and modularity of your TCA implementation:\n\n```swift\nvar body: some ReducerProtocol\u003cState, Action\u003e\n    // More reducers ...\n    Reduce { state, action in\n        switch action {\n        // More actions ...\n        case .cart(let action):\n            switch action {\n            case .didPressCloseButton:\n                return closeCart(state: \u0026state)\n            case .dismissSuccessAlert:\n                resetProductsToZero(state: \u0026state)\n\n                return .task {\n                    .closeCart\n                }\n            }\n        case .closeCart:\n            return closeCart(state: \u0026state)\n        }\n    }\n}\n\nprivate func closeCart(\n        state: inout State\n) -\u003e Effect\u003cAction, Never\u003e {\n    state.shouldOpenCart = false\n    state.cartState = nil\n\n    return .none\n}\n\nprivate func resetProductsToZero(\n    state: inout State\n) {\n    for id in state.productListState.map(\\.id)\n    where state.productListState[id: id]?.count != 0  {\n        state.productListState[id: id]?.addToCartState.count = 0\n    }\n}\n```\n\n\u003e For more about private actions, check out this [video](https://youtu.be/7BkZX_7z-jw).\n\n3. Invoke the UI\n\n\u003cimg src=\"./Images/alertView1.png\" width=\"50%\" height=\"50%\"\u003e\n\n```swift\nlet store: Store\u003cCartListDomain.State, CartListDomain.Action\u003e\n\nText(\"Parent View\")\n.alert(\n    self.store.scope(state: \\.confirmationAlert, action: { $0 }),\n    dismiss: .didCancelConfirmation\n)\n```\n\n\u003e Explicit action is always needed for `store.scope`. Check out this commit to learn more: https://github.com/pointfreeco/swift-composable-architecture/commit/da205c71ae72081647dfa1442c811a57181fb990\n\nThis [video](https://youtu.be/U3EMduy-DhE) explains more about AlertView in SwiftUI and TCA.\n\n### Making a Root Domain with Tab View\n\nCreating a Root Domain in TCA is similar to creating any other domain. In this case, each property within the state will correspond to a complex substate. To handle tab logic, we can include an enum that represents each tab item, providing a structured approach to managing the different tabs:\n\n```swift\nstruct RootDomain: ReducerProtocol {\n    struct State: Equatable {\n        var selectedTab = Tab.products\n        var productListState = ProductListDomain.State()\n        var profileState = ProfileDomain.State()\n    }\n    \n    enum Tab {\n        case products\n        case profile\n    }\n    \n    enum Action: Equatable {\n        case tabSelected(Tab)\n        case productList(ProductListDomain.Action)\n        case profile(ProfileDomain.Action)\n    }\n    \n    // Dependencies\n    var fetchProducts: @Sendable () async throws -\u003e [Product]\n    var sendOrder:  @Sendable ([CartItem]) async throws -\u003e String\n    var fetchUserProfile:  @Sendable () async throws -\u003e UserProfile\n    var uuid: @Sendable () -\u003e UUID\n    \n    static let live = Self(\n        fetchProducts: APIClient.live.fetchProducts,\n        sendOrder: APIClient.live.sendOrder,\n        fetchUserProfile: APIClient.live.fetchUserProfile,\n        uuid: { UUID() }\n    )\n    \n    var body: some ReducerProtocol\u003cState, Action\u003e {\n        Reduce { state, action in\n            switch action {\n            case .productList:\n                return .none\n            case .tabSelected(let tab):\n                state.selectedTab = tab\n                return .none\n            case .profile:\n                return .none\n            }\n        }\n        Scope(state: \\.productListState, action: /RootDomain.Action.productList) {\n            ProductListDomain(\n                fetchProducts: fetchProducts,\n                sendOrder: sendOrder,\n                uuid: uuid\n            )\n        }\n        Scope(state:  \\.profileState, action: /RootDomain.Action.profile) {\n            ProfileDomain(fetchUserProfile: fetchUserProfile)\n        }\n    }\n}\n```\n\nWhen it comes to the UI implementation, it closely resembles the standard SwiftUI approach, with a small difference. Instead of using a regular property, we hold the `store` property to manage the currently selected tab:\n\n```swift\nstruct RootView: View {\n    let store: Store\u003cRootDomain.State, RootDomain.Action\u003e\n    \n    var body: some View {\n        WithViewStore(self.store) { viewStore in\n            TabView(\n                selection: viewStore.binding(\n                    get: \\.selectedTab,\n                    send: RootDomain.Action.tabSelected\n                )\n            ) {\n                ProductListView(\n                    store: self.store.scope(\n                        state: \\.productListState,\n                        action: RootDomain.Action\n                            .productList\n                    )\n                )\n                .tabItem {\n                    Image(systemName: \"list.bullet\")\n                    Text(\"Products\")\n                }\n                .tag(RootDomain.Tab.products)\n                ProfileView(\n                    store: self.store.scope(\n                        state: \\.profileState,\n                        action: RootDomain.Action.profile\n                    )\n                )\n                .tabItem {\n                    Image(systemName: \"person.fill\")\n                    Text(\"Profile\")\n                }\n                .tag(RootDomain.Tab.profile)\n            }\n        }\n    }\n}\n```\n\nTo call RootView, we provide the initial domain state and the reducer:\nTo instantiate the `RootView`, you need to provide two parameters: the initial domain state and the reducer:\n\n```swift\n@main\nstruct OnlineStoreTCAApp: App {\n    var body: some Scene {\n        WindowGroup {\n            RootView(\n                store: Store(\n                    initialState: RootDomain.State(),\n                    reducer: RootDomain.live\n                )\n            )\n        }\n    }\n}\n```\n\nThese elements enable the proper initialization and functioning of the `RootView` within the TCA architecture.\n\n\u003e For a comprehensive understanding of this implementation, I recommend checking out this [video](https://youtu.be/a_FwMVIhCHY).\n\n## Contact\nIf you have any feedback, I would love to hear from you. Please feel free to reach out to me through any of my social media channels:\n\n* [Youtube](https://youtube.com/@swiftandtips)\n* [Twitter](https://twitter.com/swiftandtips)\n* [LinkedIn](https://www.linkedin.com/in/pedrorojaslo/)\n* [Mastodon](https://iosdev.space/@swiftandtips)\n\nThanks for reading, and have a great day! 😄\n","funding_links":[],"categories":["Misc"],"sub_categories":["SwiftUI"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpitt500%2FOnlineStoreTCA","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpitt500%2FOnlineStoreTCA","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpitt500%2FOnlineStoreTCA/lists"}