Question 6
Single source of truth and duplicated state
Users see different counts on the list and detail screens after an edit. Architecturally, how do you prevent multiple 'caches of truth' for the same entity?
Follow-ups
- What about SwiftUI
@Statevs shared store?
Answer outline
Mismatched counts happen when the same entity lives in multiple mutable places. The list screen caches one copy, the detail screen another, and a write to one never reaches the other.
The fix is a single source of truth: one place owns the entity, every screen reads from it, and every write goes through it.
Separate canonical state from view-local state. Canonical state is the shared model that must agree across screens, such as a like count, a user profile, or a cart total. View-local state is scoped to one view and temporary, such as draft text, a loading flag, or whether a sheet is showing.
Once an edit is committed, it updates the shared store. Every view then derives its display from that store instead of holding its own copy.
Principles
- Store each entity once, and have every screen read from that same source rather than a local copy.
- Route every write through the store, so views never mutate shared data directly.
- Keep canonical state (shared, must be consistent) apart from view-local state (temporary, scoped to one view).
@Statefits view-local state, and entities that appear on more than one screen belong in a shared observable store.
The store owns the array, and views call update rather than mutating their own copies:
@Observable
final class PostStore {
private(set) var posts: [Post] = []
func update(_ post: Post) {
guard let index = posts.firstIndex(where: { $0.id == post.id })
else { return }
posts[index] = post // one write, and every observer sees it
}
}
Both views receive the same PostStore instance, and neither keeps its own copy of the data:
struct PostListView: View {
let store: PostStore
var body: some View {
List(store.posts) { PostRow(post: $0) }
}
}
struct PostDetailView: View {
let store: PostStore
let postId: Post.ID
var post: Post? { store.posts.first { $0.id == postId } }
var body: some View {
Text("Likes: \(post?.likeCount ?? 0)")
Button("Like") {
if var p = post {
p.likeCount += 1
store.update(p) // the list view updates automatically
}
}
}
}
Follow-up angles
@Stateis the right fit for sheet visibility, draft text, and selection. That state lives and dies with one view and doesn't need to agree with anything else.- For persistence-backed entities, let SwiftData or Core Data be the store and query from there. View models shouldn't cache the results in separate properties.



