Dependent Queries
A dependent query fires only after another query has successfully loaded its data. This is the pattern for sequential data-fetching: load a session first, then load the user profile; load a project, then load its members.
dependsOn
Set QoraOptions.dependsOn to the key of the prerequisite query. Qora subscribes internally and triggers the dependent fetch when data becomes available.
// Step 1: fetch the session
client.watchQuery<Session>(
key: ['session'],
fetcher: () => api.getSession(),
);
// Step 2: fires automatically once ['session'] has data
client.watchQuery<User>(
key: ['user'],
fetcher: () => api.getUser(session!.userId),
options: QoraOptions(dependsOn: ['session']),
);
Behaviour per API
| API | Behaviour when dependency is unresolved |
|---|---|
watchQuery | Subscribes reactively; fires when dependency reaches Success |
fetchQuery | Throws StateError immediately. Call after dependency resolution. |
prefetch | Silently skips. Safe to call speculatively. |
In Flutter: QoraBuilder
The cleanest Flutter pattern is to nest a QoraBuilder for the dependent query inside the success case of the parent:
QoraBuilder<Session>(
queryKey: ['session'],
fetcher: () => api.getSession(),
builder: (context, sessionState, refetch) => switch (sessionState) {
Loading() || Initial() => const CircularProgressIndicator(),
Failure(:final error) => ErrorWidget('$error'),
Success(:final data) => QoraBuilder<User>(
queryKey: ['user', data.userId],
fetcher: () => api.getUser(data.userId),
builder: (context, userState, _) => switch (userState) {
Success(:final data) => ProfileCard(data),
Loading() => const CircularProgressIndicator(),
Failure(:final error) => ErrorWidget('$error'),
Initial() => const SizedBox.shrink(),
},
),
},
)
The inner QoraBuilder only mounts when the session is ready, so the dependent fetch never fires prematurely.
Using dependsOn with watchQuery
When both queries need to be active simultaneously (e.g. in a repository layer without Flutter), use dependsOn:
class UserRepository {
final QoraClient _client;
Stream<QoraState<User>> watchCurrentUser() {
// Ensure the session query is running
_client.watchQuery<Session>(
key: ['session'],
fetcher: () => api.getSession(),
);
// This query waits for ['session'] to have data before fetching
return _client.watchQuery<User>(
key: ['user'],
fetcher: () async {
final session = _client.getQueryData<Session>(['session'])!;
return api.getUser(session.userId);
},
options: const QoraOptions(dependsOn: ['session']),
);
}
}
Chaining Multiple Dependencies
A query can only declare a single dependsOn. For a chain of three queries, nest the dependsOn at each step:
// 1: session
client.watchQuery<Session>(key: ['session'], fetcher: () => api.getSession());
// 2: user (depends on session)
client.watchQuery<User>(
key: ['user'],
fetcher: () => api.getUser(session!.userId),
options: const QoraOptions(dependsOn: ['session']),
);
// 3: projects (depends on user)
client.watchQuery<List<Project>>(
key: ['projects'],
fetcher: () => api.getProjects(user!.orgId),
options: const QoraOptions(dependsOn: ['user']),
);
fetchQuery and Dependencies
fetchQuery throws immediately if the dependency is not resolved. Use it only after you know the dependency has data:
// Only safe to call once ['session'] is in Success state
final user = await client.fetchQuery<User>(
key: ['user'],
fetcher: () => api.getUser(session!.userId),
options: QoraOptions(dependsOn: ['session']),
);
For reactive code, prefer watchQuery: it handles the timing automatically.
Related
- QoraOptions API:
dependsOn,enabled - QoraBuilder: Widget-level query binding
- Quick Start