-
Hi everyone! First, I want to thank the Point-Free team (Brandon Williams & Stephen Celis) for their incredibly useful library collection and educational content. Secondly, unfortunately, not all topics come easily to me yet, and I'm not confident about my understanding of Bindings. So here's my question: I have a searchField view that roughly looks like this: private var searchField: some View {
HStack (spacing: 0) {
Image(systemName: "magnifyingglass")
.padding(.leading)
TextField(String(localized: "Search").localized(), text: $store.searchText)
.autocorrectionDisabled()
.textInputAutocapitalization(.never)
.padding()
if !store.searchText.isEmpty {
Button(action: {
store.send(.clearSearchText)
}) {
Image(systemName: "multiply.circle.fill")
}
.padding(.trailing)
}
}
.contentShape(Rectangle())
} All effects are correctly defined in the Reducer and I won't be showing those. There are many screens that need similar search functionality, so I want to make this component reusable. At the moment, I've implemented it like this: struct SearchField<R: Reducer>: View
where R.State: ObservableState, R.Action: BindableAction, R.Action.State == R.State {
@Perception.Bindable var store: StoreOf<R>
let keyPath: WritableKeyPath<R.State, String>
let clearAction: R.Action?
init(
store: StoreOf<R>,
keyPath: WritableKeyPath<R.State, String>,
clearAction: R.Action? = nil
) {
self.store = store
self.keyPath = keyPath
self.clearAction = clearAction
}
var body: some View {
HStack (spacing: 0) {
Image(systemName: "magnifyingglass")
.padding(.leading)
TextField(String(localized: "Search").localized(), text: Binding(get: {
store.state[keyPath: keyPath]
}, set: { newValue in
store.send(.binding(.set(keyPath, newValue)))
}))
.autocorrectionDisabled()
.textInputAutocapitalization(.never)
.padding()
if !store.state[keyPath: keyPath].isEmpty {
Button(action: {
if let clearAction = clearAction {
store.send(clearAction)
}
}) {
Image(systemName: "multiply.circle.fill")
}
.padding(.trailing)
}
}
.contentShape(Rectangle())
}
} And implementation in final view started to look like: SearchField<<#R: Reducer#>>(
store: store,
keyPath: \.searchText,
clearAction: .clearSearchText
) It works, but I have 2 questions:
I'll be grateful for any advice! |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 1 reply
-
Hi @EvgenySai, there are a few ways this can be improved. First, you don't need to make your view generic over the reducer, it can just be generic over the state and actions: struct SearchField<State, Action>: View
where State: ObservableState, Action: BindableAction, Action.State == State {
@Perception.Bindable var store: Store<State, Action>
…
} But more significantly, I would encourage you to reconsider whether such a simple reusable component needs to have any TCA in it at all. We never encourage adding TCA to every component throughout an entire app. There is a time and place for TCA, and typically it is at the level of a complex feature. This is where one gets the benefits of TCA, such as controlled dependencies, explicit side-effects, testability, etc. But for something as simple as a textfield, I don't think you are getting any of those benefits. Can just a SwiftUI binding work well enough? struct SearchField: View {
@Binding var text: String
…
} You can even expose a callback closure for the "clear" functionality if its logic is more complex than simply setting struct SearchField: View {
@Binding var text: String
var onClearText: () -> Void
// …
} Then you would be able to use this in a TCA feature like so: SearchField(text: $store.searchText) {
store.send(.clearSearchButtonTapped)
} A big part of utilizing TCA fully is knowing the places where it is not appropriate to use. And I feel like this is a great example of where you do not need any of the power of TCA, and can just use vanilla SwiftUI. |
Beta Was this translation helpful? Give feedback.
Hi @EvgenySai, there are a few ways this can be improved. First, you don't need to make your view generic over the reducer, it can just be generic over the state and actions:
But more significantly, I would encourage you to reconsider whether such a simple reusable component needs to have any TCA in it at all. We never encourage adding TCA to every component throughout an entire app. There is a time and place for TCA, and typically it is at the level of a complex feature. This is where one gets the benefits of TC…