Recipes

Dependent Queries

Use QoraOptions.dependsOn to chain queries so one fires only after another resolves.

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.

main.dart
// 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

APIBehaviour when dependency is unresolved
watchQuerySubscribes reactively; fires when dependency reaches Success
fetchQueryThrows StateError immediately. Call after dependency resolution.
prefetchSilently 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:

profile_screen.dart
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:

user_repository.dart
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:

main.dart
// 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:

main.dart
// 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.


Copyright © 2026