Last active
June 1, 2024 16:18
-
-
Save oliverfoggin/0bcf83016b6ad6eee8e2d961a82a6a0c to your computer and use it in GitHub Desktop.
I've removed a lot of the external code from this as I didn't have access to those. But this compiles now...
This file contains 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 SwiftUI | |
import ComposableArchitecture | |
enum ViewState { | |
case initial | |
case loading | |
case success | |
case failure(any Error) | |
} | |
struct ItemLight: Equatable, Identifiable { | |
let id: String | |
var name: String | |
} | |
struct MyError: Error { | |
} | |
@Reducer | |
struct ItemListReducer { | |
@ObservableState | |
struct State { | |
var status: ViewState = .initial | |
var items: [ItemLight] = [] | |
var hasNextPage = false | |
} | |
enum Action: ViewAction { | |
case view(View) | |
case delegate(Delegate) | |
case fetchResponse(Result<[ItemLight], any Error>) | |
@CasePathable | |
enum View { // It is common practice to move view actions to their own enum like this... (And then TCA helps in the view also). | |
// case fetchFeed | |
case scrolledToLastItem // name the view action after what happened to trigger it. | |
case onAppear | |
case refresh | |
case itemButtonTapped(ItemLight) | |
} | |
@CasePathable | |
enum Delegate { | |
case showItem(ItemLight) | |
} | |
} | |
var body: some ReducerOf<Self> { | |
Reduce<State, Action> { state, action in | |
switch action { | |
case .view(.itemButtonTapped(let item)): | |
return .send(.delegate(.showItem(item))) | |
case .view(.onAppear): | |
state.status = .loading | |
return fetchFeedEffect | |
// Don't send actions back into the reducder to perform shared logic. Extract it to a function possibly. | |
// return .send(.fetchFeed) | |
// renamed action to reflect the view action that triggered it. | |
// case .fetchFeed: | |
case .view(.scrolledToLastItem): | |
return fetchFeedEffect | |
case .fetchResponse(.success(let items)): | |
state.status = .success | |
state.items.append(contentsOf: items) | |
return .none | |
case .fetchResponse(.failure(let error)): | |
print(error.localizedDescription) | |
return .none | |
case .view(.refresh): | |
return fetchFeedEffect | |
case .delegate: | |
return .none // delegate actions aren't used here. So return none for any of them | |
} | |
} | |
} | |
var fetchFeedEffect: Effect<Action> { | |
.run { send in | |
await send(.fetchResponse(.success([]))) | |
} | |
} | |
} | |
@ViewAction(for: ItemListReducer.self) // this means you can just do `send(.someAction)` | |
struct ItemListView: View { | |
@Environment(\.colorScheme) var colorScheme | |
let store: StoreOf<ItemListReducer> | |
init(store: StoreOf<ItemListReducer>) { | |
self.store = store | |
} | |
var body: some View { | |
switch store.status { // don't need to use `store.state.xxx` just access `store.xxx`. | |
case .initial: | |
Color.clear.onAppear { | |
send(.onAppear) | |
} | |
case .loading: | |
ProgressView() | |
case .failure(let error): | |
Text(error.localizedDescription) | |
.refreshable { | |
await send(.refresh).finish() | |
} | |
case .success: | |
if !store.items.isEmpty { | |
ScrollView(.vertical) { | |
VStack { | |
LazyVStack { | |
ForEach(store.items) { item in // this is just one item. Call it `item` not `items`. | |
Button(action: { | |
send(.itemButtonTapped(item)) | |
}, label: { | |
// ItemPreview(items: items) // Again... this preview displays a single `item`. Not multiple `items`. | |
Text(item.name) | |
.frame(height: 280) | |
.frame(minHeight: 280) | |
}) | |
.onAppear { | |
if store.hasNextPage != false { | |
if item == store.items.last { // again... singular `item` | |
send(.scrolledToLastItem) | |
} | |
} | |
} | |
} | |
} | |
.refreshable { | |
await send(.refresh).finish() | |
} | |
} | |
} | |
} else { | |
Text("No items available") | |
.refreshable { | |
await send(.refresh).finish() | |
} | |
} | |
} | |
} | |
} |
This file contains 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 SwiftUI | |
import ComposableArchitecture | |
@Reducer | |
struct ItemReducer { | |
@ObservableState | |
struct State { | |
var item: ItemLight | |
} | |
} | |
@Reducer | |
struct ParentReducer { | |
@Reducer | |
enum Path { | |
case itemList(ItemListReducer) | |
case item(ItemReducer) | |
} | |
@ObservableState | |
struct State { | |
var path = StackState<Path.State>() | |
var itemList = ItemListReducer.State() | |
} | |
enum Action { | |
case path(StackAction<Path.State, Path.Action>) | |
case itemList(ItemListReducer.Action) | |
} | |
init() {} | |
var body: some ReducerOf<Self> { | |
Scope(state: \.itemList, action: \.itemList) { | |
ItemListReducer() | |
} | |
Reduce<State, Action> { state, action in | |
switch action { | |
// ItemList | |
case .itemList(.delegate(.showItem(let item))): // Only deal with the delegate actions here. | |
state.path.append(.item(ItemReducer.State(item: item))) | |
return .none | |
case .itemList: | |
return .none | |
// Nested ItemList | |
case .path(.element(id: _, action: .itemList(.delegate(.showItem(let item))))): // again, only deal with the delegate actions | |
state.path.append(.item(ItemReducer.State(item: item))) | |
return .none | |
// Nested Item | |
// case .path(.element(id: _, action: .item(.delegate(.showList))): // Again, this should be a delegate action. | |
// state.path.append(.itemList(ItemListReducer.State())) | |
// return .none | |
case .path: | |
return .none | |
} | |
} | |
.forEach(\.path, action: \.path) | |
} | |
} | |
struct ParentReducerView: View { | |
@Bindable var store: StoreOf<ParentReducer> | |
public init(store: StoreOf<ParentReducer>) { | |
self.store = store | |
} | |
var body: some View { | |
NavigationStack(path: $store.scope(state: \.path, action: \.path)) { | |
ItemListView(store: store.scope(state: \.itemList, action: \.itemList)) | |
} destination: { store in | |
switch store.case { | |
case .itemList(let store): | |
ItemListView(store: store) | |
case .item(let store): | |
ItemView(store: store) | |
} | |
} | |
} | |
} | |
struct ItemView: View { | |
let store: StoreOf<ItemReducer> | |
var body: some View { | |
Text(store.item.name) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Some of the changes I made...
ViewAction
protocol available in TCA.store.state.xyz
in favour ofstore.xyz
.item
(notitems
)..run
effect.