How to use the ReducerProtocol approach for a feature whose state contains an optional enum? #1615
-
Hello all, In our app, we have modelled navigation by having a extension FooFeature {
struct State: Equatable {
var route: Route?
var navRoute: NavigationRoute? { route?.navRoute }
// ...
}
}
extension FooFeature.State {
enum Route: Equatable {
case chat(ChatFeature.State)
case participants(ParticipantsFeature.State)
// ...
var navRoute: NavigationRoute {
switch self {
case .chat(_):
return .chat
case .participants(_):
return .participants
// ...
}
}
}
enum NavigationRoute: Hashable {
case chat
case participants
// ...
}
}
extension FooFeature {
enum Action: Equatable {
case chat(ChatFeature.Action)
case participants(ParticipantsFeature.Action)
// ...
}
}
struct FooFeature: ReducerProtocol {
var body: some ReducerProtocol<State, Action> {
Reducer { state, action in
switch action {
// ...
}
}
// what goes in here ?
}
} It seems to me that this is a combination of the "optional state" and "enum state" ways of modelling state so I thought some combination of the Any help would be greatly appreciated. Thanks! Addendum: Incidentally, before the public extension Reducer {
func pullback<GlobalState, GlobalAction, GlobalEnvironment, Route>(
unwrapping toRoute: WritableKeyPath<GlobalState, Route?>,
case toRouteCase: CasePath<Route, State>,
action toLocalAction: CasePath<GlobalAction, Action>,
environment toLocalEnvironment: @escaping (GlobalEnvironment) -> Environment
) -> Reducer<GlobalState, GlobalAction, GlobalEnvironment> {
.init { globalState, globalAction, globalEnvironment in
// do we have the correct child action?
guard
let localAction = toLocalAction.extract(from: globalAction)
else { return .none }
guard
// is a child actually being presented?
let route = globalState[keyPath: toRoute],
// if so, do we have the right child/reducer pair?
let routeCase = toRouteCase.extract(from: route)
else { return .none } // if not, don't run the child reducer
// get the child state
var localState = routeCase
// make sure to pass the (possibly updated) child state back to the parent
defer { globalState[keyPath: toRoute] = toRouteCase.embed(localState) }
// run the child reducer
let effects = self.run(
&localState,
localAction,
toLocalEnvironment(globalEnvironment)
)
.map(toLocalAction.embed)
return effects
}
}
} |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 13 replies
-
Hey @wtruppel. Because of the "modifier" style approach that was selected, Reduce { state, action in
switch action {
…
}
}
.ifLet(\.route, action: /Action.route) { // Route? -> Route
EmptyReducer()
.ifCaseLet(/Route.chat, action: /Action.chat) { // Route -> Route.case
ChatFeature()
}
.ifCaseLet(/Route.participants, action: /Action.participants) {
ParticipantsFeature()
}
} Once this is working, you can try to make it work with your Another approach that may help is to "coalesce" the nilness of Footnotes
|
Beta Was this translation helpful? Give feedback.
Hey @wtruppel. Because of the "modifier" style approach that was selected,
there is no "convenient" way to scope into optional enums right now(wrong, see @stephencelis answer below). One solution is to open the optional, and then open the enum's case using anEmptyReducer
as a support:Once this is working, you can try to make it work with your
…