I would like to make reusable solution to avoid boilerplate for lazy loading features
I have base state and base view model which describes common
@ObservableState
enum BaseState<Input, BaseContentState> {
case progress(BaseProgressState<Input>)
case content(BaseContentState)
case error(BaseErrorState<Input>)
init(input: Input) {
self = .progress(BaseProgressState(input: input))
}
}
@Reducer
struct BaseViewModel<Input, ContentInput, BaseContentState: BaseContentStateType, BaseContentAction> where ContentInput == BaseContentState.ContentInput {
typealias State = BaseState<Input, BaseContentState>
enum Action {
case progress(BaseProgressAction<Input, ContentInput>)
case content(BaseContentAction)
case error(BaseErrorAction<Input>)
}
}
BaseContentStateType
allows to make content state buildable with some result of progress feature
protocol BaseContentStateType {
associatedtype ContentInput
init(contentInput: ContentInput)
}
Additional I have common implementation to show progress...
@ObservableState
struct BaseProgressState<Input> {
let input: Input
}
enum BaseProgressAction<Input, ContentInput> {
case load(Input)
case loadSuccess(ContentInput)
case loadFailure(Input, Error)
}
struct BaseProgressViewModel<Input, ContentInput>: Reducer {
typealias State = BaseProgressState<Input>
typealias Action = BaseProgressAction<Input, ContentInput>
var body: some ReducerOf<Self> {
EmptyReducer()
}
}
struct BaseProgressView<Input, ContentInput>: View {
let store: StoreOf<BaseProgressViewModel<Input, ContentInput>>
var body: some View {
ProgressView()
.onAppear {
store.send(.load(store.input))
}
}
}
... and error
@ObservableState
struct BaseErrorState<Input> {
let input: Input
let error: Error
}
enum BaseErrorAction<Input> {
case retry(Input)
}
struct BaseErrorViewModel<Input>: Reducer {
typealias State = BaseErrorState<Input>
typealias Action = BaseErrorAction<Input>
var body: some ReducerOf<Self> {
EmptyReducer()
}
}
struct BaseErrorView<Input>: View {
let store: StoreOf<BaseErrorViewModel<Input>>
var body: some View {
Button("Retry") {
store.send(.retry(store.input))
}
}
}
When I try to use this solution...
@Reducer
struct FeatureViewModel {
typealias State = BaseViewModel<...>.State
typealias Action = BaseViewModel<...>.Action
var body: some ReducerOf<Self> {
...
}
}
struct FeatureView: View {
let store: StoreOf<FeatureViewModel>
var body: some View {
BaseView(store: store) { store in
FeatureContentView(store: store)
}
}
}
... I face problems, like
Instance method 'ifLet(_:action:)' requires that 'FeatureViewModel.State' (aka 'BaseState<FeatureInput, FeatureContentViewModel.State>') conform to 'CaseReducerState'
And no matter how hard I try to get it to look right, I don't understand what I'm doing wrong. How to confirm CaseReducerState
?
I forgot to make ViewModel
for this case
.ifLet(\.$myFeatureState, action: \.myFeatureAction) {
MyFeatureViewModel()
}