Created
April 28, 2025 06:23
-
-
Save fededri/6b6345ecd96bf4d76c5856185904a7c3 to your computer and use it in GitHub Desktop.
TCA Reducer with state in parent domain and enum state
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import ComposableArchitecture | |
import SwiftUI | |
struct ContentView: View { | |
let store: StoreOf<TodoReducer> | |
var body: some View { | |
VStack { | |
Button("Change state to error") { | |
store.send(.setLoadedState(.failure(FeatureError.unknown))) | |
} | |
Button("Change state to loading") { | |
store.send(.setContentState(.loading)) | |
} | |
Button("Change state to loaded") { | |
store.send(.onAppear) | |
} | |
Text(store.someParentFeatureState) | |
Group { | |
switch store.contentState { | |
case .loading: | |
ProgressView() | |
.frame(maxWidth: .infinity, maxHeight: .infinity) | |
case .loaded: | |
if let loadedStore = store.scope( | |
state: \.contentState.loaded, | |
action: \.loaded | |
) { | |
LoadedView(store: loadedStore) | |
} | |
case .error: | |
Text("An error occurred.") | |
.foregroundColor(.red) | |
.frame(maxWidth: .infinity, maxHeight: .infinity) | |
} | |
} | |
.frame(maxWidth: .infinity, maxHeight: .infinity) | |
} | |
.onAppear(perform: { | |
store.send(.onAppear) | |
}) | |
.padding() | |
} | |
} | |
#Preview { | |
ContentView(store: Store(initialState: TodoReducer.State(), reducer: { | |
TodoReducer() | |
})) | |
} | |
@Reducer | |
struct TodoReducer: Reducer { | |
@Dependency(\.continuousClock) var clock | |
@ObservableState | |
struct State: Equatable { | |
var someParentFeatureState: String = "" | |
var contentState: ContentState = .loading | |
@CasePathable | |
@dynamicMemberLookup // Key, without this code does not compile. | |
enum ContentState: Equatable { | |
case loading | |
case loaded(Loaded.State) | |
case error(ErrorReducer.State) | |
} | |
} | |
enum Action { | |
case onAppear | |
case loaded(Loaded.Action) | |
case error(ErrorReducer.Action) | |
case setLoadedState(Result<Loaded.State, Error>) | |
case setContentState(State.ContentState) | |
} | |
var body: some ReducerOf<Self> { | |
Reduce { state, action in | |
switch action { | |
case .onAppear: | |
state.contentState = .loading | |
return .run { send in | |
await send( | |
.setLoadedState( | |
Result(catching: { | |
try await clock.sleep(for: .seconds(1)) // simulate some operation | |
// Initialize Loaded.State with dummy data here | |
return Loaded.State(list: ["Item A", "Item B", "Item C"]) | |
}) | |
)) | |
} | |
case .loaded(.userDidTapItem(let item)): | |
print("item tapped: \(item)") | |
return .none | |
case .loaded: | |
return .none | |
case .error: | |
return .none | |
case .setLoadedState(.success(let loadedState)): | |
state.contentState = .loaded(loadedState) | |
return .none | |
case .setLoadedState(.failure): | |
state.contentState = .error(.init()) | |
return .none | |
case .setContentState(let contentState): | |
state.contentState = contentState | |
return .none | |
} | |
} | |
.ifLet(\.contentState.loaded, action: \.loaded) { | |
Loaded() | |
} | |
.ifLet(\.contentState.error, action: \.error) { | |
ErrorReducer() | |
} | |
} | |
} | |
@Reducer | |
struct Loaded: Reducer { | |
@ObservableState | |
struct State: Equatable { | |
var list: [String] = [] | |
} | |
enum Action { | |
case userDidTapItem(String) | |
} | |
var body: some ReducerOf<Self> { | |
Reduce { state, action in | |
switch action { | |
case .userDidTapItem(let item): | |
print("received action in LoadedReducer: \(item)") | |
return .none | |
} | |
} | |
} | |
} | |
struct LoadedView: View { | |
let store: StoreOf<Loaded> | |
var body: some View { | |
List(store.list, id: \.self) { item in | |
Button(item) { | |
store.send(.userDidTapItem(item)) | |
} | |
} | |
} | |
} | |
@Reducer | |
struct ErrorReducer: Reducer { | |
@ObservableState | |
struct State: Equatable { | |
var errorMessage: String = "" | |
} | |
enum Action { | |
} | |
var body: some ReducerOf<Self> { | |
EmptyReducer() | |
} | |
} | |
enum FeatureError: Error { | |
case unknown | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment