{"id":18264743,"url":"https://github.com/reactcomponentkit/counter","last_synced_at":"2026-04-16T14:08:10.870Z","repository":{"id":137750978,"uuid":"145849227","full_name":"ReactComponentKit/Counter","owner":"ReactComponentKit","description":"This is ReactComponentKit example. Counter is a very simple and basic redux example.","archived":false,"fork":false,"pushed_at":"2019-09-14T01:23:09.000Z","size":1196,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-14T19:53:53.870Z","etag":null,"topics":["component","counter","mvvm","react","reactcomponentkit","redux"],"latest_commit_sha":null,"homepage":"https://github.com/ReactComponentKit/Counter","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/ReactComponentKit.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-08-23T12:17:20.000Z","updated_at":"2019-09-14T01:23:11.000Z","dependencies_parsed_at":null,"dependency_job_id":"52145b87-0cfb-4ae7-bd9b-1187a99e33dd","html_url":"https://github.com/ReactComponentKit/Counter","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/ReactComponentKit%2FCounter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ReactComponentKit%2FCounter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ReactComponentKit%2FCounter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ReactComponentKit%2FCounter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ReactComponentKit","download_url":"https://codeload.github.com/ReactComponentKit/Counter/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247958710,"owners_count":21024821,"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":["component","counter","mvvm","react","reactcomponentkit","redux"],"created_at":"2024-11-05T11:15:47.993Z","updated_at":"2026-04-16T14:08:10.814Z","avatar_url":"https://github.com/ReactComponentKit.png","language":"Swift","readme":"# Counter\n\nThis is [ReactComponentKit](https://github.com/ReactComponentKit/ReactComponentKit) example. Counter is very simple and basic redux example. \n\n## Screenshot\n\n![](art/counter.png)\n\n## What is ReactComponentKit\n\nReactComponentKit is a library for building a UIViewController based on Component. Additionary, It architect UIViewController as Redux with MVVM. If you use ReactComponentKit, You can make many scenes(UIViewController) more easily. You can share components among scenes. \n\n## Define Components\n\n### CountLabelComponent\n\n```swift\nimport Foundation\nimport ReactComponentKit\nimport SnapKit\n\nprotocol CountLabelComponentState {\n    var count: Int { get }\n}\n\nclass CountLabelComponent: UIViewComponent {\n    \n    private lazy var label: UILabel = {\n        let label = UILabel(frame: .zero)\n        label.font = UIFont.boldSystemFont(ofSize: 40)\n        label.textColor = UIColor.black\n        label.text = \"0\"\n        label.textAlignment = .center\n        return label\n    }()\n    \n    override var contentSize: CGSize {\n        return label.intrinsicContentSize\n    }\n    \n    override func setupView() {\n        addSubview(label)\n        label.snp.makeConstraints { (make) in\n            make.edges.equalToSuperview()\n        }\n        \n        // subscribe new state. The new state is dispatched to here when it updated.\n        subscribeState()\n    }\n        \n    override func on(state: State) {\n        guard let countState = state as? CountLabelComponentState else { return }\n        label.text = String(countState.count)\n    }\n}\n```\n\nUIViewComponent is just a UIView. You can layout sub views or components in the setupView method by using SnapKit. ReactComponentKit uses SnapKit to layout views. \n\n### IncrementButtonComponent \u0026 DecrementButtonComponent\n\n```swift\nimport ReactComponentKit\nimport RxSwift\nimport RxCocoa\n\nclass IncrementButtonComponent: UIViewComponent {\n    \n    var onTap: (() -\u003e Void)? = nil\n    \n    private let disposeBag = DisposeBag()\n    private lazy var button: UIButton = {\n        let button = UIButton(type: .system)\n        button.frame = .zero\n        button.setTitle(\"+\", for: [])\n        button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 30)\n\n        button\n            .rx.tap\n            .subscribe(onNext: { [weak self] in\n                self?.onTap?()\n            }).disposed(by: disposeBag)\n            \n        \n        return button\n    }()\n    \n    override func setupView() {\n        addSubview(button)\n        button.snp.makeConstraints { (make) in\n            make.edges.equalToSuperview()\n        }\n    }\n}\n```\n\n```swift\nimport ReactComponentKit\nimport RxSwift\nimport RxCocoa\n\nclass DecrementButtonComponent: UIViewComponent {\n    \n    var onTap: (() -\u003e Void)? = nil\n    \n    private let disposeBag = DisposeBag()\n    private lazy var button: UIButton = {\n        let button = UIButton(type: .system)\n        button.frame = .zero\n        button.setTitle(\"-\", for: [])\n        button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 30)\n        \n        button\n            .rx.tap\n            .subscribe(onNext: { [weak self] in\n                self?.onTap?()\n            }).disposed(by: disposeBag)\n        \n        return button\n    }()\n    \n    override func setupView() {\n        addSubview(button)\n        button.snp.makeConstraints { (make) in\n            make.edges.equalToSuperview()\n        }\n    }\n}\n```\n\nYou can make above button components more general like as ActionButtonComponent. However, I made button components separately. \n\n## Define Reducers in viewModel\n\n```swift\n...\n\nfunc increase(count: Int) {\n    setState {\n        $0.copy { $0.count += count }\n    }\n}\n    \nfunc decrease(count: Int) {\n    setState {\n        $0.copy { $0.count -= count }\n    }\n}\n\n...\n\n```\n\n\n## Define ViewModel \u0026 State\n\n```swift\nimport Foundation\nimport ReactComponentKit\n\nstruct CounterState: State, CountLabelComponentState {\n    var count: Int = 0\n    var error: RCKError? = nil\n}\n\nclass CounterViewModel: RCKViewModel\u003cCounterState\u003e {\n    \n    override func setupStore() {\n        initStore { store in\n            store.initial(state: CounterState())\n        }\n    }\n    \n    func increase(count: Int) {\n         setState {\n            $0.copy { $0.count += count }\n        }\n    }\n    \n    func decrease(count: Int) {\n        setState {\n            $0.copy { $0.count -= count }\n        }\n    }\n}\n```\n\nRootViewModelType has a redux stroe. You can define state.\n\n## Using Reducers\n\n```swift\n\n...\n\nincrementButton.onTap = { [weak self] in\n\tguard let strongSelf = self else { return }\n\tstrongSelf.viewModel.increase(count: 1)\n}\n    \ndecrementButton.onTap = { [weak self] in\n\tguard let strongSelf = self else { return }\n\tstrongSelf.viewModel.decrease(count: 1)\n}\n\n...\n\n```\n\n## Make Scene(UIViewController)\n\n```swift\nimport UIKit\nimport ReactComponentKit\n\nclass CounterViewController: UIViewController {\n    \n    private lazy var viewModel: CounterViewModel = {\n        return CounterViewModel()\n    }()\n    \n    private lazy var countLabel: CountLabelComponent = {\n        return CountLabelComponent(token: viewModel.token)\n    }()\n    \n    private lazy var incrementButton: IncrementButtonComponent = {\n        return IncrementButtonComponent(token: viewModel.token)\n    }()\n    \n    private lazy var decrementButton: DecrementButtonComponent = {\n        return DecrementButtonComponent(token: viewModel.token)\n    }()\n\n    override func viewDidLoad() {\n        super.viewDidLoad()\n        \n        view.addSubview(countLabel)\n        view.addSubview(incrementButton)\n        view.addSubview(decrementButton)\n        \n        countLabel.snp.makeConstraints { (make) in\n            make.center.equalToSuperview()\n            make.left.right.equalToSuperview()\n        }\n        \n        decrementButton.snp.makeConstraints { (make) in\n            make.left.equalToSuperview()\n            make.bottom.equalTo(view.safeAreaLayoutGuide)\n            make.width.equalToSuperview().dividedBy(2)\n            make.height.equalTo(48)\n        }\n        \n        incrementButton.snp.makeConstraints { (make) in\n            make.right.equalToSuperview()\n            make.bottom.equalTo(view.safeAreaLayoutGuide)\n            make.width.equalToSuperview().dividedBy(2)\n            make.height.equalTo(48)\n        }\n        \n        incrementButton.onTap = { [weak self] in\n            guard let strongSelf = self else { return }\n            strongSelf.viewModel.increase(count: 1)\n        }\n        \n        decrementButton.onTap = { [weak self] in\n            guard let strongSelf = self else { return }\n            strongSelf.viewModel.decrease(count: 1)\n        }\n    }\n\n    override func didReceiveMemoryWarning() {\n        super.didReceiveMemoryWarning()\n    }\n\n}\n```\n\n## MIT License\n\nThe MIT License\n\nCopyright © 2018 Sungcheol Kim, https://github.com/ReactComponentKit/Counter\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\nall copies 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\nTHE SOFTWARE.","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Freactcomponentkit%2Fcounter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Freactcomponentkit%2Fcounter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Freactcomponentkit%2Fcounter/lists"}