IfCaseLet Pullback #1296
-
I typically use an
How would you use
I might be doing it wrong but this is what I’m doing:
Is there a way to get this working or should I consider modeling the state differently? |
Beta Was this translation helpful? Give feedback.
Replies: 5 comments 8 replies
-
Maybe getting a little closer. Now with the following code I get:
Is there a way to get a |
Beta Was this translation helpful? Give feedback.
-
Hi Porter! Could provide a sample project for others to toy around with and serve as a common frame of reference? |
Beta Was this translation helpful? Give feedback.
-
I think we are missing the tool that makes it possible to first single out an enum field in your state, and then destructure it for running child reducers on each case. Without resorting to optional paths (which are still experimental and only really exist in the isowords code base), there's a hacky thing you can try: var body: some ReducerProtocol<State, Action> {
Scope(state: \.route, action: Action.route) {
EmptyReducer()
.ifCaseLet(...)
.ifCaseLet(...)
.ifCaseLet(...)
}
Reduce { state, action in
// Parent logic
}
} It's not great, but it should help you move forward. We will think more about what this tool should look like. It's also worth mentioning that the navigation APIs we are cooking up will also largely alleviate the need for this hack or optional paths, which will be nice. |
Beta Was this translation helpful? Give feedback.
-
I have a similar setup for routing, and so I've written some helpers to help with this. I've got two base protocols for the state and action: public protocol RoutableState {
associatedtype Route
var route: Route? { get set }
}
public protocol RoutableAction {
associatedtype Route
static func setRoute(_ route: Route?) -> Self
} and then this new Reducer function, with code largely based on extension ReducerProtocol where State: RoutableState, Action: RoutableAction {
func route<Destination: ReducerProtocol>(
path toLocalState: CasePath<State.Route, Destination.State>,
action toLocalAction: CasePath<Action, Destination.Action>,
@ReducerBuilderOf<Destination> then destination: () -> Destination,
file: StaticString = #file,
fileID: StaticString = #fileID,
line: UInt = #line
) -> RoutingReducer<Self, Destination> {
RoutingReducer(
parent: self,
local: destination(),
toLocalState: toLocalState,
toLocalAction: toLocalAction,
file: file,
fileID: fileID,
line: line
)
}
}
struct RoutingReducer<Parent: ReducerProtocol, Local: ReducerProtocol>: ReducerProtocol where Parent.State: RoutableState, Parent.Action: RoutableAction {
@usableFromInline let parent: Parent
@usableFromInline let local: Local
@usableFromInline let toLocalState: CasePath<Parent.State.Route, Local.State>
@usableFromInline let toLocalAction: CasePath<Parent.Action, Local.Action>
@usableFromInline let file: StaticString
@usableFromInline let fileID: StaticString
@usableFromInline let line: UInt
@inlinable
init(
parent: Parent,
local: Local,
toLocalState: CasePath<Parent.State.Route, Local.State>,
toLocalAction: CasePath<Parent.Action, Local.Action>,
file: StaticString,
fileID: StaticString,
line: UInt
) {
self.parent = parent
self.local = local
self.toLocalState = toLocalState
self.toLocalAction = toLocalAction
self.file = file
self.fileID = fileID
self.line = line
}
@inlinable
func reduce(
into state: inout Parent.State, action: Parent.Action
) -> Effect<Parent.Action, Never> {
return .merge(
self.reduceLocal(into: &state, action: action),
self.parent.reduce(into: &state, action: action)
)
}
@inlinable
func reduceLocal(
into state: inout Parent.State, action: Parent.Action
) -> Effect<Parent.Action, Never> {
guard let localAction = toLocalAction.extract(from: action) else {
return .none
}
guard var localState = toLocalState.extract(from: state.route) else {
print("Action \(localAction) was sent despite the state being nilled out")
return .none
}
defer { state.route = toLocalState.embed(localState) }
return local.reduce(into: &localState, action: localAction)
.map(toLocalAction.embed)
}
} So at the call site it ends up becoming: var body: some ReducerProtocol<State, Action> {
Reduce { /* reducer goes here */ }
.route(path: /State.Route.modal, action: /Action.modal) {
Modal()
}
} The one big "gotcha" here is that the TCA runtime warnings stuff is internal so I can't use that, and instead I'm just printing stuff to the console. |
Beta Was this translation helpful? Give feedback.
-
Was there any progress regarding this problem? In particular a state that has the following shape: struct State {
enum Status {
case x(X)
case y(Y)
}
var status: Status = ...
var somethingElseCommonToBothCases = ...
} And the corresponding helpers at the SwiftUI level with Thank you. 🙇♂️ |
Beta Was this translation helpful? Give feedback.
I think we are missing the tool that makes it possible to first single out an enum field in your state, and then destructure it for running child reducers on each case.
Without resorting to optional paths (which are still experimental and only really exist in the isowords code base), there's a hacky thing you can try:
It's not great, but it should help you move forward. We will think more about what this tool should look like.
It's also worth mentioning…