Hi, I'm trying to bind some form data in a sheet screen. The code works fine but I got the warning below in console. Could someone please tell me what I'm missing?
(I'm using prerelease/1.0
branch)
2023-03-07 00:30:35.250828+0900 Sandbox[61762:876505] [ComposableArchitecture] A "ifLet" at "Sandbox/SandboxApp.swift:59" received a presentation action when destination state was absent. …
Action:
ContentFeature.Action.subScreenAction(.presented(.binding))
This is generally considered an application logic error, and can happen for a few reasons:
• A parent reducer set destination state to "nil" before this reducer ran. This reducer must run before any other reducer sets destination state to "nil". This ensures that destination reducers can handle their actions while their state is still present.
• This action was sent to the store while destination state was "nil". Make sure that actions for this reducer can only be sent from a view store when state is present, or from effects that start from this reducer. In SwiftUI applications, use a Composable Architecture view modifier like "sheet(store:…)".
2023-03-07 00:30:35.251176+0900 Sandbox[61762:876505] [ComposableArchitecture] A binding action sent from a view store at "Sandbox/SandboxApp.swift:73" was not handled. …
Action:
SubContentFeature.Action.binding(.set(_, ))
To fix this, invoke "BindingReducer()" from your feature reducer's "body".
2023-03-07 00:30:35.251393+0900 Sandbox[61762:876505] [ComposableArchitecture] A "ifLet" at "Sandbox/SandboxApp.swift:59" received a presentation action when destination state was absent. …
Action:
ContentFeature.Action.subScreenAction(.presented(.binding))
This is generally considered an application logic error, and can happen for a few reasons:
• A parent reducer set destination state to "nil" before this reducer ran. This reducer must run before any other reducer sets destination state to "nil". This ensures that destination reducers can handle their actions while their state is still present.
• This action was sent to the store while destination state was "nil". Make sure that actions for this reducer can only be sent from a view store when state is present, or from effects that start from this reducer. In SwiftUI applications, use a Composable Architecture view modifier like "sheet(store:…)".
2023-03-07 00:30:35.251652+0900 Sandbox[61762:876505] [ComposableArchitecture] A binding action sent from a view store at "Sandbox/SandboxApp.swift:73" was not handled. …
Action:
SubContentFeature.Action.binding(.set(_, ))
To fix this, invoke "BindingReducer()" from your feature reducer's "body".
Code
//
// SandboxApp.swift
// Sandbox
import ComposableArchitecture
import SwiftUI
@main
struct SandboxApp: App {
var body: some Scene {
WindowGroup {
ContentView(store: .init(initialState: .init(), reducer: ContentFeature()))
}
}
}
struct ContentView: View {
var store: StoreOf<ContentFeature>
var body: some View {
WithViewStore(store, observe: { $0 }) { viewStore in
VStack {
Button {
viewStore.send(.didTapStartButton)
} label: {
Text("Start")
}
}
.padding()
.sheet(store: store.scope(state: \.$subScreenState, action: ContentFeature.Action.subScreenAction)) { store in
SubContentView(store: store)
}
}
}
}
struct ContentFeature: ReducerProtocol {
struct State: Equatable {
@PresentationState var subScreenState: SubContentFeature.State?
}
enum Action {
case didTapStartButton
case subScreenAction(PresentationAction<SubContentFeature.Action>)
}
var body: some ReducerProtocol<State, Action> {
Reduce { state, action in
switch action {
case .didTapStartButton:
state.subScreenState = .init()
return .none
default:
return .none
}
}
.ifLet(\.$subScreenState, action: /Action.subScreenAction) {
SubContentFeature()
}
}
}
struct SubContentView: View {
var store: StoreOf<SubContentFeature>
var body: some View {
WithViewStore(store, observe: { $0 }) { viewStore in
NavigationView {
VStack {
TextField("Text", text: viewStore.binding(\.$text))
.padding()
}.toolbar {
Button {
viewStore.send(.didTapDoneButton)
} label: {
Text("Done")
}
}.navigationTitle("Sub Screen")
}
}
}
}
struct SubContentFeature: ReducerProtocol {
@Dependency (\.dismiss) var dismiss
struct State: Equatable {
@BindingState var text = ""
var dummy = true
}
enum Action: BindableAction {
case binding(BindingAction<State>)
case didTapDoneButton
}
var body: some ReducerProtocol<State, Action> {
BindingReducer()
Reduce { state, action in
switch action {
case .didTapDoneButton:
return .fireAndForget {
await dismiss()
}
default:
return .none
}
}
}
}