{"id":28214555,"url":"https://github.com/goodrequest/goodreactor","last_synced_at":"2025-07-26T08:37:27.902Z","repository":{"id":81791998,"uuid":"593150568","full_name":"GoodRequest/GoodReactor","owner":"GoodRequest","description":"⚛️ GoodReactor is a Redux-inspired Reactor framework for iOS developed using Swift. It enables seamless communication between the View Model, View Controller, and Coordinator through state and navigation functions. It ensures no side-effects by interacting with dependencies outside of the Reduce function. Integrate it with using SPM!","archived":false,"fork":false,"pushed_at":"2025-06-20T13:31:26.000Z","size":416,"stargazers_count":32,"open_issues_count":5,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-06-20T14:31:56.705Z","etag":null,"topics":["ios","library","reactorkit","swift"],"latest_commit_sha":null,"homepage":"","language":"Swift","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/GoodRequest.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","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,"zenodo":null}},"created_at":"2023-01-25T11:05:22.000Z","updated_at":"2025-06-20T13:25:35.000Z","dependencies_parsed_at":"2024-10-25T17:47:00.188Z","dependency_job_id":"b49f9b4c-6cf0-4021-ad34-aa0cc46e5bb7","html_url":"https://github.com/GoodRequest/GoodReactor","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/GoodRequest/GoodReactor","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GoodRequest%2FGoodReactor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GoodRequest%2FGoodReactor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GoodRequest%2FGoodReactor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GoodRequest%2FGoodReactor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/GoodRequest","download_url":"https://codeload.github.com/GoodRequest/GoodReactor/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GoodRequest%2FGoodReactor/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":267141069,"owners_count":24041978,"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","status":"online","status_checked_at":"2025-07-26T02:00:08.937Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["ios","library","reactorkit","swift"],"created_at":"2025-05-17T21:08:01.827Z","updated_at":"2025-07-26T08:37:27.894Z","avatar_url":"https://github.com/GoodRequest.png","language":"Swift","readme":"![Logo](good-reactor.png)\n \n# GoodReactor\n\n[Check out the Documentation](https://goodrequest.github.io/GoodReactor/documentation/goodreactor/)\n\n[![iOS Version](https://img.shields.io/badge/iOS_Version-\u003e=_12.0-brightgreen?logo=apple\u0026logoColor=green)]() \n[![Swift Version](https://img.shields.io/badge/Swift_Version-5.5-green?logo=swift)](https://docs.swift.org/swift-book/)\n[![Supported devices](https://img.shields.io/badge/Supported_Devices-iPhone/iPad-green)]()\n[![Contains Test](https://img.shields.io/badge/Tests-YES-blue)]()\n[![Dependency Manager](https://img.shields.io/badge/Dependency_Manager-SPM-red)](#swiftpackagemanager)\n\nGoodReactor is an adaptation of the Reactor framework that is Redux inspired.\nThe view model communicates with the view controller via the State and with the Coordinator via the navigation function.\nYou communicate to the viewModel via Actions\nViewmodel changes state in the Reduce function\nViewmodel interactes with dependencies outside of the Reduce function not to create side-effects\n\nLink to the original reactor kit: https://github.com/ReactorKit/ReactorKit\n\n# Installation\n## Swift Package Manager\n\nCreate a `Package.swift` file and add the package dependency into the dependencies list.\nOr to integrate without package.swift add it through the Xcode add package interface.\n\n```swift\nimport PackageDescription\n\nlet package = Package(\n    name: \"SampleProject\",\n    dependencies: [\n        .package(url: \"https://github.com/GoodRequest/GoodReactor\" .upToNextMajor(\"2.0.0\"))\n    ]\n)\n\n```\n\n# Usage\n## GoodReactor\n\n### ViewModel\nIn your ViewModel define Actions, Mutations, Destinations and the State\n\n- State defines all data of a View (or a ViewController)\n- Action represents user actions that are sent from the View.\n- Mutation represents state changes from external sources.\n- Destination represents all possible destinations, where user can navigate.\n\n```swift\n@Observable final class ViewModel: Reactor {\n    enum Action {\n        case login(username: String, password: String)\n    }\n\n    enum Mutation {\n        case didReceiveAuthResponse(Credentials)\n    }\n\n    enum Destination {\n        case homeScreen\n        case errorAlert\n    }\n\n    @Observable final class State {\n        var username: String\n        var password: String\n    }\n}\n```\n\nYou can provide the initial state of the view in the `makeInitialState` function.\n\n```swift\nfunc makeInitialState() -\u003e State {\n    return State()\n}\n```\n\nFinally in the `reduce` function you define how `state` changes, according to certain `event`s:\n\n```swift\ntypealias Event = GoodReactor.Event\u003cAction, Mutation, Destination\u003e\n\nfunc reduce(state: inout State, event: Event) {\n    switch event.kind {\n    case .action(.login(...)):\n        // ...\n\n    case .mutation:\n        // ...\n\n    case .destination:\n        // ...\n    }\n}\n```\n\nYou can run asynchronous tasks by using `run` and returning the result in form of a `Mutation`.\n\n```swift\nfunc reduce(state: inout State, event: Event) {\n    switch event.kind {\n    case .action(.login(let username, let password)):\n        run(event) {\n            let credentials = await networking.login(username, password)\n            return Mutation.didReceiveAuthResponse(credentials)\n        }\n\n    // ...\n\n    case .mutation(.didReceiveAuthResponse(let credentials)):\n        // proceed with login\n    }\n}\n```\n\nYou can listen to external changes by `subscribe`-ing to event `Publisher`-s.\nYou start the subscriptions by calling the `start()` function.\n\n```swift\n// in ViewModel:\nfunc transform() {\n    subscribe {\n        await ExternalTimer.shared.timePublisher\n    } map: {\n        Mutation.didChangeTime(seconds: $0)\n    }\n}\n\n// in View (SwiftUI):\nvar body: some View {\n    MyContentView()\n        .task { viewModel.start() }\n}\n```\n\n### View (SwiftUI)\n\nYou add the ViewModel as a property wrapper to your view:\n\n```swift\n@ViewModel private var model = MyViewModel()\n```\n\nTo access the current `State` you use:\n\n```swift\n// read-only access\nText(model.username)\n\n// binding (refactored to a variable for better readability)\nlet binding = model.bind(\\.username, action: { .setUsername($0) })\nTextField(\"Username\",  text: binding)\n```\n\nTo send an event to the ViewModel you call:\n\n```swift\nmodel.send(action: .login(username, password))\nmodel.send(destination: .errorAlert)\n```\n\n### UIViewController (UIKit/Combine)\n\nFrom `UIViewController` (in UIKit, or any other frameworks) you can send actions to ViewModel via Combine:\n```swift\nmyButton.publisher(for: .touchUpInside).map { _ in .login(username, password) }\n    .map { .action($0) }\n    .subscribe(model.eventStream)\n    .store(in: \u0026cancellables)\n```\n\nThen use Combine to subscribe to state changes, so every time the state is changed, ViewController can be updated as well:\n```swift\nreactor.stateStream\n    .map { String($0.username) }\n    .assign(to: \\.text, on: usernameLabel, ownership: .weak)\n    .store(in: \u0026cancellables)\n```\n\n## Logging\n```swift\nstruct SampleLogger: ReactorLogger {\n    \n    func logReactorEvent(_ message: Any, level: LogLevel, fileName: String, lineNumber: Int) {\n        print(\"[\\(level)] \\(message) (\\(fileName):\\(lineNumber))\")\n    }\n    \n}\n\nReactorConfiguration.logger = SampleLogger()\n```\n\n# License\nGoodReactor repository is released under the MIT license. See [LICENSE](LICENSE.md) for details.\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgoodrequest%2Fgoodreactor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgoodrequest%2Fgoodreactor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgoodrequest%2Fgoodreactor/lists"}