Updating a parent feature from a multiple child features #2774
-
Hi everyone, struct Page {
var blocks: [Block]
}
struct Block: Identifiable {
let id: Int
var blockType: BlockType
var isHeader: Bool {
switch self.blockType {
case .header: return true
case .banner: return false
}
}
var isBanner: Bool {
switch self.blockType {
case .header: return false
case .banner: return true
}
}
}
enum BlockType {
case header(Header)
case banner(Banner)
}
struct Header: Equatable {
var name: String
}
struct Banner: Equatable {
var url: String
}
var mock = Page(blocks: [
.init(id: 1, blockType: .header(.init(name: "John"))),
.init(id: 2, blockType: .banner(.init(url: "www.google.com")))
]) So I have a feature that is responsible to bridge the child and the parent features. @Reducer
struct EditFeature {
@ObservableState
enum State {
case header(HeaderFeature.State)
case banner(BannerFeature.State)
}
enum Action {
case header(HeaderFeature.Action)
case banner(BannerFeature.Action)
}
var body: some ReducerOf<Self> {
Scope(state: \.header, action: \.header) { HeaderFeature() }
Scope(state: \.banner, action: \.banner) { BannerFeature() }
}
} Right now I am relying on delegates to update the parent view and I am not sure if I am updating the parent view the right way @Reducer
struct HeaderFeature {
@ObservableState
struct State {
var header: Header
}
enum Action: BindableAction {
case delegate(Delegate)
case binding(BindingAction<State>)
enum Delegate {
case update(Header)
}
}
var body: some ReducerOf<Self> {
BindingReducer()
Reduce { state, action in
switch action {
case .binding:
return .run { [header = state.header] send in
await send(.delegate(.update(header)))
}
case .delegate:
return .none
}
}
}
}
struct HeaderView: View {
@Bindable var store: StoreOf<HeaderFeature>
var body: some View {
TextField("Name", text: $store.header.name).presentationDetents([.medium])
}
}
@Reducer
struct BannerFeature {
// pretty much the same as HeaderFeature
} And finally this is my main feature that holds those blocks and @Reducer
struct ParentFeature {
@ObservableState
struct State {
var blocks: [Block]
@Presents var editBlock: EditFeature.State?
}
enum Action {
case tappedBlock(Block)
case editBlock(PresentationAction<EditFeature.Action>)
}
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .tappedBlock(let block):
switch block.blockType {
case .header(let header):
state.editBlock = .header(.init(header: header))
case .banner(let banner):
state.editBlock = .banner(.init(banner: banner))
}
return .none
case .editBlock(.presented(.header(.delegate(.update(let header))))):
guard let index = state.blocks.firstIndex(where: { $0.isHeader }) else { return .none }
state.blocks[index].blockType = .header(header)
return .none
case .editBlock(.presented(.banner(.delegate(.update(let banner))))):
guard let index = state.blocks.firstIndex(where: { $0.isBanner }) else { return .none }
state.blocks[index].blockType = .banner(banner)
return .none
case .editBlock:
return .none
}
}
.ifLet(\.$editBlock, action: \.editBlock) {
EditFeature()
}
}
}
struct ParentFeatureView: View {
@Bindable var store: StoreOf<ParentFeature>
var body: some View {
VStack(spacing: 60) {
ForEach(store.blocks) { block in
Button {
store.send(.tappedBlock(block))
} label: {
switch block.blockType {
case .header(let header):
Text(header.name)
case .banner(let banner):
Text(banner.url)
}
}
}
Spacer()
}
.sheet(item: $store.scope(state: \.editBlock?.header, action: \.editBlock.header)) { store in
HeaderView(store: store)
}
.sheet(item: $store.scope(state: \.editBlock?.banner, action: \.editBlock.banner)) { store in
BannerView(store: store)
}
}
} As you can see I have to duplicate Here is gist of the whole code https://gist.github.com/nvprokofiev/5dc54282e2fb67624a78352116105086 Is there a more efficient way to achieve this without relying on delegates, or have I constructed my model in the wrong way? Any suggestions or improvements would be greatly appreciated. Thanks! |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment
-
Hello @nvprokofiev, it is very common question that has been discussed a lot. I think it is better to model the editing data as a dependency. Child feature mutates this data and uses publisher interface of this data. Parent feature works with the dependency that allows to subscribe and read this data. In this case it is not necessary to travel back and forth through hierarchy of nested features and the situation is much simpler. You can read discussions and examples of this common problem: |
Beta Was this translation helpful? Give feedback.
Hello @nvprokofiev, it is very common question that has been discussed a lot.
I think it is better to model the editing data as a dependency. Child feature mutates this data and uses publisher interface of this data. Parent feature works with the dependency that allows to subscribe and read this data. In this case it is not necessary to travel back and forth through hierarchy of nested features and the situation is much simpler.
You can read discussions and examples of this common problem:
#1898 (comment)
#2564