DevYeom / OneWay

A Swift library for state management with unidirectional data flow.
https://swiftpackageindex.com/DevYeom/OneWay/2.9.0/documentation/oneway
MIT License
78 stars 8 forks source link

Add Binder for swiftui #63

Closed sobabear closed 9 months ago

sobabear commented 9 months ago

Related Issues πŸ’­

I want to make sugar for just setter value on State for ViewStore when i use SwiftUI. Thus I suggest binder using keypath to setter for oneway

Description πŸ“

Lets suppose i use simple textfield app

import Foundation
import SwiftUI
import OneWay

struct OneWayView1: View {
    @StateObject var store: ViewStore<OneWayReducer1> = .init(reducer: .init(), state: .init())
    var body: some View {
        VStack {

            TextField("TextField", text: $store.state.searchText)
                .task {

                    for await searchText in store.states.searchText {
                        // Problem line1
                        print("πŸ₯Ά\(#file) \(#line) \(searchText)")
                    }
                }

            Button("buttonbutton") {
                store.send(.tapButtonForDebug)
            }
        }
    }
}

class OneWayReducer1: Reducer {
    enum Action {
        case setSearchText(String)
        case tapButtonForDebug

    }

    struct State: Equatable {
        var searchText: String = ""
    }

    func reduce(state: inout State, action: Action) -> AnyEffect<Action> {
        switch action {
        case let .setSearchText(value):
            state.searchText = value
            return .none

        case .tapButtonForDebug:
            // Problem line2
            print("πŸ₯Ά\(#file) \(#line) \(state.searchText)")
            return .none
        }
    }
}

I marked two line which both prints out. And when i types like "Hello OneWay", Problem Line1 would be print " H", "He".... "Hello OneWay". However when i clicked, button which leads to Action.tapButtonForDebug prints out nothing but "" <- which is state.searchText.

So on this problem, i mostly used to handle this problem like adding Binder like


extension OneWayView1 {
    var searchTextBinder: Binding<String> {
        .init {
            store.state.searchText
        } set: { newText in
            store.send(.setSearchText(newText))
        }
    }
}

You know whenever it is getting more and more properties on State, this binder makes code much larger, thats why i open this PR.

Expected result would be better than above code.

TextField("TextField", text: store.binder(\.searchText))

Additional Notes πŸ“š

Checklist βœ…

sobabear commented 9 months ago

I wrote none of annotation for documentation of usage, unless this function would be suitable. if it is ok, i will do with your opnion

DevYeom commented 9 months ago

@sobabear

Thanks!

When passing a binding, it is correct to directly call the store's send() as you suggested.

TextField(
    "TextField",
    text: Binding(
        get: { store.state.searchText },
        set: { store.send(.setSearchText($0)) }
    )
)

The syntax sugar you suggested looks very nice. However, OneWay avoids having UI-related dependencies to maintain a lightweight library. It seems better to create a separate module for UI Extension or to include it in your own project.

// In your own UI extension module or wrapping module
@_exported import OneWay