Skip to content

Commit 4319b4a

Browse files
committed
Updated documentation
1 parent 1b9e48a commit 4319b4a

File tree

5 files changed

+112
-384
lines changed

5 files changed

+112
-384
lines changed

Guides/Composing Reducers.md

Lines changed: 0 additions & 72 deletions
This file was deleted.

Guides/Getting Started.md

Lines changed: 51 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,26 @@ SwiftDux helps build SwiftUI-based applications around an [elm-like architecture
1313

1414
## State
1515

16-
The state is a single, immutable structure acting as the single source of truth within the application.
16+
The state is an immutable structure acting as the single source of truth within the application.
1717

1818
Below is an example of a todo app's state. It has a root `AppState` as well as an ordered list of `TodoState` objects.
1919

2020
```swift
2121
import SwiftDux
2222

23-
struct AppState: StateTyoe {
23+
struct AppState: Equatable {
2424
todos: OrderedState<TodoItem>
2525
}
2626

27-
struct TodoItem: IdentifiableState {
27+
struct TodoItem: Equatable, Identifiable {
2828
var id: String,
2929
var text: String
3030
}
3131
```
3232

3333
## Actions
3434

35-
An action is a description of how the state will change. They're typically dispatched from events in the application. This could be a user interacting with the application or a service API receiving updates. Swift's enum type is ideal for actions, but structs and classes could be used as well.
35+
An action is a dispatched event to mutate the application's state. Swift's enum type is ideal for actions, but structs and classes could be used as well.
3636

3737
```swift
3838
import SwiftDux
@@ -46,10 +46,7 @@ enum TodoAction: Action {
4646

4747
## Reducers
4848

49-
A reducer consumes an action to produce a new state. The `Reducer` protocol has two primary methods to override:
50-
51-
- `reduce(state:action:)` - For actions supported by the reducer.
52-
- `reduceNext(state:action:)` - Dispatches an action to any sub-reducers. This method is optional.
49+
A reducer consumes an action to produce a new state.
5350

5451
```swift
5552
final class TodosReducer: Reducer {
@@ -67,34 +64,18 @@ final class TodosReducer: Reducer {
6764
}
6865
return state
6966
}
70-
7167
}
7268
```
7369

74-
Here's an example of a root reducer dispatching to a subreducer.
75-
76-
```swift
77-
final class AppReducer: Reducer {
78-
let todosReducer = TodosReducer()
79-
80-
func reduceNext(state: AppState, action: TodoAction) -> AppState {
81-
State(
82-
todos: todosReducer.reduceAny(state.todos, action)
83-
)
84-
}
85-
86-
}
87-
```
88-
89-
Reducers can also be combined together. This is useful when multiple root reducers are needed, such as two reducers from separate modules.
70+
Reducers can also be added together to form a composite reducer.
9071

9172
```swift
9273
let combinedReducer = AppReducer + NavigationReducer
9374
```
9475

9576
## Store
9677

97-
The store acts as the container of the state. It needs to be initialized with a default state and a root reducer. Then inject it into the application using the `provideStore(_:)` view modifier.
78+
The store manages the state and notifies the views of any updates.
9879

9980
```swift
10081
import SwiftDux
@@ -106,121 +87,63 @@ window.rootViewController = UIHostingController(
10687
)
10788
```
10889

109-
## Connectable View
90+
## ConnectableView
11091

111-
The `ConnectableView` protocol provides a slice of the application state to your views using the functions `map(state:)` and `body(props:)`. The `@MappedDispatch` property wrapper injects an `ActionDispatcher` to send actions to the store.
92+
The `ConnectableView` protocol provides a slice of the application state to your views using the functions `map(state:)` or `map(state:binder:)`.
11293

11394
```swift
11495
struct TodosView: ConnectableView {
115-
@MappedDispatch() private var dispatch
96+
struct Props: Equatable {
97+
var todos: [TodoItem]
98+
}
11699

117100
func map(state: AppState) -> OrderedState<Todo>? {
118-
state.todos
101+
Props(todos: state.todos)
119102
}
120103

121104
func body(props: OrderedState<Todo>): some View {
122105
List {
123106
ForEach(todos) { todo in
124107
TodoItemRow(item: todo)
125108
}
126-
.onDelete { self.dispatch(TodoAction.removeTodos(at: $0)) }
127-
.onMove { self.dispatch(TodoAction.moveTodos(from: $0, to: $1)) }
128109
}
129110
}
130111
}
131112
```
132113

133-
The view can later be placed like any other.
134-
135-
```swift
136-
struct RootView: View {
137-
138-
var body: some View {
139-
TodosView()
140-
}
141-
}
142-
```
143-
144114
## ActionBinding<_>
145115

146-
SwiftUI has a focus on two-way bindings that connect to a single value source. To support updates through actions, SwiftDux provides a convenient API in the `ConnectableView` protocol using an `ActionBinder` object. Use the `map(state:binder:)` method on the protocol as shown below. It provides a value to the text field and dispatches an action when the text value changes. It also binds a function to a dispatchable action.
116+
Using the `map(state:binder:)` method on the `ConnectableView` protocol to bind an action to the props object. It can also be used to bind an updatable state value
117+
with an action.
147118

148119
```swift
149-
struct LoginForm: View {
150-
120+
struct TodosView: ConnectableView {
151121
struct Props: Equatable {
152-
@ActionBinding var email: String
153-
@ActionBinding var onSubmit: ()->()
122+
var todos: [TodoItem]
123+
@ActionBinding var newTodoText: String
124+
@ActionBinding var addTodo: ()->()
154125
}
155126

156-
func map(state: AppState, binder: ActionBinder) -> Props? {
127+
func map(state: AppState, binder: ActionBinder) -> OrderedState<Todo>? {
157128
Props(
158-
email: binder.bind(state.loginForm.email) {
159-
LoginFormAction.setEmail($0)
160-
},
161-
onSubmit: binder.bind(LoginFormAction.submit)
129+
todos: state.todos,
130+
newTodoText: binder.bind(state.newTodoText) { TodoAction.setNewTodoText($0) },
131+
addTodo: binder.bind { TodoAction.addTodo() }
162132
)
163133
}
164134

165-
func body(props: Props) -> some View {
166-
VStack {
167-
TextField("Email", text: $props.email)
168-
/* ... */
169-
Button(action: props.onSubmit) {
170-
Text("Submit")
135+
func body(props: OrderedState<Todo>): some View {
136+
List {
137+
TextField("New Todo", text: props.$newTodoText, onCommit: props.addTodo)
138+
ForEach(todos) { todo in
139+
TodoItemRow(item: todo)
171140
}
172141
}
173142
}
174143
}
175144
```
176-
177-
## Passing Data to a Connectable View
178-
In some cases, a connected view needs external information to map the state to its props, such as an identifier. Simply add any needed variables to your view and access them in the mapping function.
179-
180-
```swift
181-
struct TodoDetailsView: ConnectableView {
182-
var id: String
183-
184-
func map(state: TodoList) -> Todo? {
185-
state[id]
186-
}
187-
}
188-
189-
// Somewhere else in the view hierarchy:
190-
191-
TodoDetailsView(id: "123")
192-
```
193-
194-
## Previewing Connected Views
195-
To preview a connected view by itself, you can provide a store that contains the parent state and reducer it maps from. This preview is based on a view in the Todo List Example project. Make sure to add `provideStore(_:)` after the connect method.
196-
197-
```swift
198-
#if DEBUG
199-
public enum TodoRowContainer_Previews: PreviewProvider {
200-
static var store: Store<TodoList> {
201-
Store(
202-
state: TodoList(
203-
id: "1",
204-
name: "TodoList",
205-
todos: .init([
206-
Todo(id: "1", text: "Get milk")
207-
])
208-
),
209-
reducer: TodosReducer()
210-
)
211-
}
212-
213-
public static var previews: some View {
214-
TodoRowContainer(id: "1")
215-
.provideStore(store)
216-
}
217-
218-
}
219-
#endif
220-
```
221-
222145
## Action Plans
223-
An `ActionPlan` is a special kind of action that can be used to group other actions together or perform any kind of async logic.
146+
An `ActionPlan` is a special kind of action that can be used to group other actions together or perform any kind of async logic outside of a reducer.
224147

225148
```swift
226149
/// Dispatch multiple actions together synchronously:
@@ -234,7 +157,7 @@ let plan = ActionPlan<AppState> { store in
234157
/// Perform async operations:
235158

236159
let plan = ActionPlan<AppState> { store in
237-
userLocationService.getLocation { location
160+
userLocationService.getLocation { location in
238161
store.send(LocationAction.updateLocation(location))
239162
}
240163
}
@@ -253,51 +176,30 @@ let plan = ActionPlan<AppState> { store, completed in
253176
dispatch(plan)
254177
```
255178

256-
#
257-
258-
## Query External Services
259-
Action plans can be used in conjunction with the `onAppear(dispatch:)` view modifier to connect to external data sources when a view appears. If the action plan returns a publisher, it will automatically cancel when the view disappears. Optionally, use `onAppear(dispatch:cancelOnDisappear:)` if the publisher should continue.
260-
261-
Action plans can also subscribe to the store. This is useful when the query needs to be refreshed if the application state changes. Rather than imperatively handling this by re-sending the action plan, it can be done more declaratively within it.
262-
263-
Here's an example of an action plan that queries for todos. It updates whenever the filter changes. It also debounces to reduce the amount of queries sent to the external services.
179+
## Previewing Connected Views
180+
To preview a connected view by itself use the `provideStore(_:)` method inside the preview.
264181

265182
```swift
266-
enum TodoListAction {
267-
...
268-
}
269-
270-
extension TodoListAction {
271-
272-
static func getState(from store: Store<AppState>) -> some Publisher {
273-
Just(store.state).merge(with: store.didChange.map { store.state })
274-
}
275-
276-
static func queryTodos() -> ActionPlan<AppState> {
277-
ActionPlan<AppState> { store, completed in
278-
getState(from: store)
279-
.map { $0.filterBy }
280-
.removeDuplicates()
281-
.debounce(for: .milliseconds(300), scheduler: RunLoop.main)
282-
.flatMap { filter in self.services.queryTodos(filter: filter) }
283-
.catch { _ in Just<[TodoItem]>([]) }
284-
.map { todos -> Action in TodoListAction.setTodos(todos) }
285-
.send(to: store, receivedCompletion: completed)
286-
}
287-
}
288-
}
289-
290-
struct TodoListView: ConnectableView {
291-
292-
func map(state: AppState) -> [TodoItem]? {
293-
state.todoList.items
183+
#if DEBUG
184+
public enum TodoRowContainer_Previews: PreviewProvider {
185+
static var store: Store<TodoList> {
186+
Store(
187+
state: TodoList(
188+
id: "1",
189+
name: "TodoList",
190+
todos: .init([
191+
Todo(id: "1", text: "Get milk")
192+
])
193+
),
194+
reducer: TodosReducer()
195+
)
294196
}
295-
296-
func body(props: [TodoItem]) -> some View {
297-
renderTodos(todos: props)
298-
.onAppear(dispatch: TodoListAction.queryTodos())
197+
198+
public static var previews: some View {
199+
TodoRowContainer(id: "1")
200+
.provideStore(store)
299201
}
300-
301-
// ...
202+
302203
}
204+
#endif
303205
```

Guides/Installation.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Xcode
44

5-
Use the new swift package manager integration to include the library.
5+
Search for SwiftDux in Xcode's Swift Package Manager integration.
66

77
## Package.swift
88

@@ -13,7 +13,7 @@ import PackageDescription
1313

1414
let package = Package(
1515
dependencies: [
16-
.Package(url: "https://github.com/StevenLambion/SwiftDux.git", majorVersion: 1, minor: 3)
16+
.Package(url: "https://github.com/StevenLambion/SwiftDux.git", from: "2.0.0")
1717
]
1818
)
1919
```

0 commit comments

Comments
 (0)