I have a situation in which I have a Reducer which needs to needs to be combined with the same optional sub-reducer for two distinct optional states, only one of which is ever non-nil at any point. If I combine the sub-reducer twice, I get "Action sent to reducer where state is nil" errors in whichever of the two currently has the nil state.
I'll try to explain the scenario: I have a flow which can lead to a generic "form filling" flow, where the pages and fields of the form are driven by the backend. There are two states in the parent reducer that can lead to this form flow, only one of which can ever be non-nil at any given time:
struct FormState {
// state properties
}
enum FormAction {
// form actions
}
struct FormEnvironment {}
let formReducer: Reducer<FormState, FormAction, FormEnvironment> = // form reducer
struct ParentState {
var fooFormState: FormState?
var barFormState: FormState?
}
enum ParentAction {
case form(FormAction)
// other cases
}
let parentReducer: Reducer<ParentState, ParentAction, ParentEnvironment> =
.combine(
formReducer
.optional()
.pullback(
state: \.fooFormState,
action: /ParentAction.form,
environment: const(FormEnvironment())
),
formReducer
.optional()
.pullback(
state: \.barFormState,
action: /ParentAction.form,
environment: const(FormEnvironment())
)
)
In the above example, if fooFormState
is non-nil, and the form screen is showing, any action from the form will also be passed to the second instance of the formReducer
for barFormState
, which is nil, and therefore throws an assertion about actions being sent to nil states.
The only way so far I have been able to work around this is to combine fooFormState
and barFormState
into a single formState
computed property and only combine the formReducer
once:
enum FormType {
case foo
case bar
}
struct FormState {
// state properties
var type: FormType
}
enum FormAction {
// form actions
}
struct FormEnvironment {}
let formReducer: Reducer<FormState, FormAction, FormEnvironment> = // form reducer
struct ParentState {
var fooFormState: FormState?
var barFormState: FormState?
var formState: FormState? {
get {
if let state = fooFormState {
return state
}
return barFormState
}
set {
guard let value = newValue else { return }
switch value.type {
case .foo:
fooFormState = value
case .bar:
barFormState = value
}
}
}
}
enum ParentAction {
case form(FormAction)
// other cases
}
let parentReducer: Reducer<ParentState, ParentAction, ParentEnvironment> =
.combine(
formReducer
.optional()
.pullback(
state: \.formState,
action: /ParentAction.form,
environment: const(FormEnvironment())
)
)
I was wondering if there was a better way to express this. I would ideally want to have something that expresses: only run the formReducer
if Either barFormState
or fooFormState
is non-nil, and in particular, they should not both be non-nil simultaneously.