-
-
Notifications
You must be signed in to change notification settings - Fork 65
StateObject anti-pattern info #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9d2e2975eb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| ### @StateObject instantiation in View's initializer | ||
| If you need to create a @StateObject with initialization parameters in your view's custom initializer, be aware of redundant allocations and hidden side effects. | ||
|
|
||
| This approach is an anti-pattern in general. Prefer storing the StateObject in the parent view or wherever the model is actually owned, then pass it down (as an StateObject direct or as binding) to keep ownership and lifecycle explicit. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid suggesting passing a @StateObject to child views
The new guidance says to “pass it down (as an StateObject direct or as binding)”, but child views should not declare the same instance as @StateObject; they should use @ObservedObject, @EnvironmentObject, or @Bindable (for @Observable) while ownership stays in the parent. Suggesting @StateObject or a binding here can mislead readers into duplicating ownership or expecting bindings for reference types, which breaks the lifecycle clarity this section is trying to enforce.
Useful? React with 👍 / 👎.
|
It's better to say use such a thing with Parent View (Main View screen) because in such case it is valid useCase and will not break ownership lifeCycle. But don't say it is anti pattern because one of the core Philosophy of this skill is that we don't to force any specific pattern or way of writing code. |
|
It’s not valid, see the discussion. |
I saw the discussion and the thing is: when we init the @StateObject in the init of the View using StateObject(initial: _), all what we are doing is the following setting the default value of the State doing so doesn't break lifecycle of the State at all. What makes it a problem is when you expect the State to update when you change the value given to the view, but in case of you know that this is the parent view (Main View of the screen) and it takes a parameter as an input (not a state) and you use it in the init it's safe and valid approach, putting a side any architecture approach. See reference (StateObject is the same as State different is StateObject is for objects): |
|
I'm not sure where those pages are from but those pages and your explanation are the exact anti-pattern we need the skill to avoid. SwiftUI is the architecture and the architecture requires lifting the state into parent View and pass Binding down for write access (or let if its read only). Explained more in the discussion here: e.g. |
|
When you add a parameter (initial value ) for a view this doesn't mean we are removing the state from the parent view, all what it does is giving initial value of the state the same as the initial vale we put to the state after =. So Actually doing so has zero effect and it's a valid SwiftUI code that doesn't break the framework, maybe it's not the best architecture discussion and we don't include or force any architecture preferences in this skill. And by the way this book is created by objc.io specifically by chris eidhof. He is one of the top people in SwiftUI check him out. |
|
Passing an initial value to a child's @State creates a 'Split Source of Truth.' Once the child View is initialized, the parent can no longer update that value. The child 'owns' its own copy now, and the two will drift apart, creating a real mess to get back in sync. Could you show the specific use case you're working on? It's better to update the parent’s state before the child view appears, rather than trying to sync two separate states. And that can actually be tricky for someone new to SwiftUI, it might need the use of a struct or a computed Binding. |
|
I've made a snippet to play with based on ObjC SwiftUI example: import SwiftUI
struct Counter: View {
init(value: Int = 0) {
_value = State(wrappedValue: value)
}
@State private var value: Int
var body: some View {
Button("Increment: \(value)") {
value += 1
}
}
}
struct StateBindView: View {
@State private var value: Int = 0
var body: some View {
Divider()
Counter(value: value)
Divider()
Counter(value: value)
.id(value)
Divider()
Counter(value: 5)
Divider()
Button("Increment: \(value)") {
value += 1
}
}
}
#Preview {
StateBindView()
}As we can see, it decouples from rendering tree if we will use such init. Last paragraph says: it's not very useful. My suggestion: Let's remove "anti-pattern sentence" suggested by Codex. |



From discussion in previous PR: #7
Need to highlight in Skill that using such init, in general, is not very good pattern and breaks the logical scope.
CC @malhal