Scoping to struct-wrapped action enums with CaseKeyPath #3391
-
We're having trouble refactoring from the closure-based scoping function: public func scope<ChildState, ChildAction>(
state toChildState: @escaping (_ state: State) -> ChildState,
action fromChildAction: @escaping (_ childAction: ChildAction) -> Action
) -> Store<ChildState, ChildAction> to the KeyPath-based one: public func scope<ChildState, ChildAction>(
state: KeyPath<State, ChildState>,
action: CaseKeyPath<Action, ChildAction>
) -> Store<ChildState, ChildAction> Our problem is that we have some Action enums that wrap their nested values in structs (we do this using a Macro that gives us something similar to private actions). For example, we might generate code that looks something like this: @Reducer
struct Feature {
struct State {
var childState = ChildFeature.State()
}
@CasePathable
enum Action {
case protected(ProtectedContainer)
@CasePathable
enum Protected {
case child(ChildFeature.Action)
}
struct ProtectedContainer {
fileprivate let action: Action.Protected
fileprivate init(_ action: Action.Protected) {
self.action = action
}
}
}
}
@Reducer
struct ChildFeature {
struct State { }
@CasePathable
enum Action { }
} If we then create a store like this: let store: StoreOf<Feature> = .init(initialState: .init(), reducer: { }) We don't know how to scope that store to a Reducer over the child action: let childStore: StoreOf<ChildFeature> = store.scope(state: \.childState, action: /* What goes here? */) We've tried several approaches, such as constructing the |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 2 replies
-
Hmm... if your action is indeed filePrivate then I'm not sure there's anything that you could put in there to make that work. |
Beta Was this translation helpful? Give feedback.
-
Hi @skyler-cricut, you essentially need to make your Here are the basics (I also squashed a layer of nesting because it didn't seem important, but if you do need that you can bring it back): @Reducer
struct Feature {
struct State {
var childState = ChildFeature.State()
}
@CasePathable
enum Action {
case protected(ProtectedContainer)
struct ProtectedContainer: CasePathable {
fileprivate let action: ChildFeature.Action
fileprivate init(_ action: ChildFeature.Action) {
self.action = action
}
struct AllCasePaths {
fileprivate var child: AnyCasePath<ProtectedContainer, ChildFeature.Action> {
AnyCasePath(
embed: { .init($0) },
extract: { $0.action }
)
}
}
static let allCasePaths = AllCasePaths()
}
}
}
@Reducer
struct ChildFeature {
struct State { }
@CasePathable
enum Action { }
}
@MainActor
func foo() {
let store: StoreOf<Feature> = .init(initialState: .init(), reducer: { })
let childStore: StoreOf<ChildFeature> = store.scope(state: \.childState, action: \.protected.child)
} Note that I even made the Also note that this is all work that the |
Beta Was this translation helpful? Give feedback.
Hi @skyler-cricut, you essentially need to make your
ProtectedContainer
into aCasePathable
type so that you can continue chaining deeper into these structures. It's a little strange because clearly it's a struct, not an enum, but a struct with a single field is indistinguishable from an enum with a single field, and so it is straightforward to do.Here are the basics (I also squashed a layer of nesting because it didn't seem important, but if you do need that you can bring it back):