Skip to content

Core Concepts

Understanding the fundamental concepts of Fasq will help you build better applications and avoid common pitfalls.

A query is a declarative dependency on an asynchronous data source. Queries are tied to a unique QueryKey and can be used to fetch, cache, and synchronize data.

  1. Idle: Query hasn’t started yet.
  2. Loading: Initial fetch is in progress.
  3. Success: Data is available.
  4. Error: Fetch failed.

Fasq uses QueryKey to identify cache entries. The simplest way to create one is using the .toQueryKey() extension on strings.

// Simple key
'users'.toQueryKey()
// Parameterized key (using string interpolation)
'user:$userId'.toQueryKey()
// Hierarchical key
'posts:user:$userId:page:$page'.toQueryKey()

For type safety, you can use TypedQueryKey.

// Strongly typed key for List<User>
const usersKey = TypedQueryKey<List<User>>('users');

Mutations are for creating, updating, or deleting data. Unlike queries:

  • They are active only when triggered (via mutate()).
  • They do not auto-refetch.
  • They are one-off operations.
MutationBuilder<User, String>(
mutationFn: (name) => api.createUser(name),
builder: (context, state, mutate) {
return ElevatedButton(
onPressed: () => mutate('New Name'),
child: Text('Create'),
);
},
)

Fasq implements a Stale-While-Revalidate strategy.

The duration data is considered “fresh”.

  • If data is fresh, Fasq returns it from cache without a network request.
  • If data is stale, Fasq returns it from cache immediately, but triggers a background refetch.
  • Default: Duration.zero (always stale).

The duration inactive data remains in memory.

  • If a query is unmounted (e.g., user leaves the screen), the data sits in cache for this duration.
  • If accessed again within this time, it’s reused.
  • If not accessed, it is garbage collected.
  • Default: 5 minutes.
QueryOptions(
staleTime: Duration(minutes: 5), // Trust cache for 5 minutes
cacheTime: Duration(minutes: 30), // Keep in memory for 30 minutes
)

If multiple widgets request the same queryKey at the same time, Fasq sends only one network request. Once the request completes, all widgets update with the data simultaneously.

// Widget A
QueryBuilder(queryKey: 'data'.toQueryKey(), ...)
// Widget B (same key)
QueryBuilder(queryKey: 'data'.toQueryKey(), ...)
// -> 1 Fetch only!

The QueryClient acts as the single source of truth. It holds the cache and manages all active queries.

// Access globally
final client = QueryClient();
// Invalidate specific data
client.invalidateQuery('users'.toQueryKey());
// Prefetch data
client.prefetchQuery('profile'.toQueryKey(), fetchProfile);