Intermediate · 15 min
Share server state between screens
One entry per key, stale values that stay visible, and optimistic edits that roll back.
You’ll buildTwo screens rendering the same resource from one request, with mutations that update both.
1
Step 1
Ask by key
A fresh value comes back without touching the network; a stale one comes back immediately and refreshes behind it. Two screens asking at the same moment share one fetch.
let queries = QueryClient()
let user: User = try await queries.value(for: QueryKey("users", 1)) {
try await client.load(request: request, authScope: nil)
}2
Step 2
Render four states
Errors are part of the state, not only thrown, so a view can show the problem beside the value it already had.
for await state in await queries.states(for: QueryKey("users", 1), as: User.self) {
switch state {
case .idle, .loading(nil): showSpinner()
case let .loading(.some(user)),
let .success(user, _): show(user, stale: state.isStale)
case let .failure(error, previous): show(error, keeping: previous)
}
}3
Step 3
Mutate optimistically
A failure restores the exact snapshot captured before the change — not a reversed diff, which stops being correct as soon as two mutations race.
try await queries.mutate(
optimistic: [QueryKey("users", 1): editedUser],
invalidating: [QueryKey("users", 1), "users"]
) {
try await client.load(request: saveRequest, authScope: nil)
}4
Step 4
Page a list
Accumulated pages land in the cache under the query key, so the screen subscribes the way it subscribes to anything else.
let feed = PagedQuery<Post, String>(key: "feed", client: queries) { cursor in
let page: FeedPage = try await client.load(
request: feedRequest(after: cursor), authScope: nil
)
return QueryPage(elements: page.posts, nextCursor: page.next)
}
let posts = try await feed.loadNextPage()Tutorial complete