How to send an action with a closure as a parameter #438
Replies: 5 comments 9 replies
-
Can you share some code to show why you need to do this ? Maybe there's an alternative solution. |
Beta Was this translation helpful? Give feedback.
-
It all depends what you're doing inside the callback, really. If you're using it to update something in your view, you could change that property on whatever If you're doing some asynchronous work, then you could instead drive that via an Ultimately, the solution depends on what you're trying to achieve. Could you share your ideas, and perhaps some more code, to work out what the correct way to do it in TCA is? |
Beta Was this translation helpful? Give feedback.
-
Here's what we are trying to accomplish. We have a class (PowerMeter) that is used elsewhere in our program. This class needs to expose a programatic API with a way to get feedback since the task may fail. Currently we use a closure, but something else could deliver the Result. Otherwise, the PowerMeter also has an associated view that uses the Store and the change in wavelength is handled by the store through and action. final class PowerMeter: PhysicalDeviceWrapper {
private var store: Store<PowerMeterState, PowerMeterAction>
private var viewStore: ViewStore<PowerMeterState, PowerMeterAction>
init() {
self.store = Store(
initialState: PowerMeterState(),
reducer: powerMeterReducer,
environment: PowerMeterEnvironment()
)
self.viewStore = ViewStore(store)
}
func set(wavelength: Double, completion: @escaping (Result<Response, Never>) -> Void) {
/// What to do with the closure?
viewStore.send(.userSetCorrectionWavelength(wavelength))
}
} |
Beta Was this translation helpful? Give feedback.
-
OK, is the struct PowerMeterEnvironment {
var powerMeter: PowerMeter
}
enum PowerMeterAction: Equatable {
case setWavelength(Double)
case setWavelengthResult(Result<Response, Never>)
}
let powerMeterReducer = Reducer<PowerMeterState, PowerMeterAction, PowerMeterEnvironment> { state, action, environment in
switch action {
case let .setWavelength(wavelength):
return .result { completion in
environment.powerMeter.setWavelength(wavelength: wavelength, completion: completion)
}
.eraseToEffect()
.map(PowerMeterAction.setWavelengthResult)
case let .setWavelengthResult(result):
// Do something with the result here
return .none
} |
Beta Was this translation helpful? Give feedback.
-
Another option, in addition to everything discussed above, is to hold func set(wavelength: Double, completion: @escaping (Result<Response, Never>) -> Void) {
/// What to do with the closure?
viewStore.send(.userSetCorrectionWavelength(wavelength))
// call the closure with data held in state after sending the action:
if let response = viewStore.response {
completion(viewStore.response)
}
} This is pretty much the only way to handle APIs that have completion callbacks like this, unless you can refactor the API in something that is more friendly for the environment. There are a few Apple APIs that follow this pattern. Things like background tasks and widgets have similar APIs. |
Beta Was this translation helpful? Give feedback.
-
In order to start implementing TCA in our large code base, I would like to do something like this:
But unfortunately, having an action with a callback as a parameter makes the Action enum unable to conform to Equatable.
Beta Was this translation helpful? Give feedback.
All reactions