How do I update a variable as a side effect of a state change in VSM? #46
-
Hey folks, I came across the following question that was sent directly to me and I thought I'd answer it here for visibility. Original Question:
|
Beta Was this translation helpful? Give feedback.
Replies: 1 comment
-
VSM state changes in SwiftUI are automatically handled when you declare your state variable with the However, sometimes you need to synchronize a SwiftUI State, Binding, or StateObject variable with the VSM state. There are a few ways to do this: Using onChange to Synchronize a State VariableYou can attach a closure to be called when the state changes, if the state is Equatable. struct DemoView: View {
@ViewState var state: DemoViewState = //...
@State var textFieldValue: String = ""
var body: some View {
VStack {
switch state {
case .loading:
Text("Loading...")
case .editing(let model):
//...
TextField("Enter text here", text: $textFieldValue)
Button("Tap me") {
$state.observe(model.save(textFieldValue))
}
}
}
.onChange(of: state) { _ in
if case .editing(let model) = state {
textFieldValue = model.text
}
}
}
} Using onReceive to Synchronize a State VariableIf your state is not equatable, you can subscribe to state changes with onReceive and use the state container's "didSetPublisher" or "willSetPublisher" to trigger the update. Continuing from the previous code example, this would replace the onChange: .onReceive($state.didSetPublisher) { _ in
if case .editing(let model) = state {
textFieldValue = model.text
}
} Synchronizing State with a Custom BindingIf you need to synchronize the state to a SwiftUI binding for things like presenting or hiding a sheet, this can be done with a custom binding. struct DemoView: View {
@ViewState var state: DemoViewState
var isErrorShown: Binding<Bool> {
Binding {
if case .savingError = state {
return true
}
return false
} set: { newValue in
if case .savingError(let model) = state {
$state.observe(model.toggleError(to: newValue))
}
}
}
var body: some View {
VStack {
// ...
}
.sheet(isPresented: isErrorShown, content: {
// ...
})
}
} BonusSometimes you just need to change the shape of your state to more easily integrate with SwiftUI views. You can do this by adding an extension to your state: extension DemoViewState {
var hasError: Bool {
switch self {
case .loadingError, .savingError:
return true
default:
return false
}
}
} Each time the state is updated by an action, any SwiftUI element that uses this extension will update as well. struct DemoView: View {
@ViewState var state: DemoViewState
var body: some View {
VStack {
// ...
}
.sheet(isPresented: .constant(state.hasError), content: {
// ...
})
}
} |
Beta Was this translation helpful? Give feedback.
VSM state changes in SwiftUI are automatically handled when you declare your state variable with the
@ViewState
property wrapper and observe the output of your actions ($state.observe(model.load())
). This means that whenever a new state is produced, it automatically calls thebody
property on your SwiftUI view and updates all subviews.However, sometimes you need to synchronize a SwiftUI State, Binding, or StateObject variable with the VSM state. There are a few ways to do this:
Using onChange to Synchronize a State Variable
You can attach a closure to be called when the state changes, if the state is Equatable.