Skip to main content

useFuture

Listens to a Future and exposes its current state as an AsyncSnapshot. It is the hook equivalent of the FutureBuilder widget.

Widget buildProfile(BuildContext context, Future<Profile> request) {
final snapshot = useFuture(request); // <- Listens to the Future, rebuilds on completion

if (snapshot.hasError) return const Text('Failed to load');
final profile = snapshot.data;
return Text(profile?.name ?? 'Loading...'); // <- null until the Future completes
}

Signature

AsyncSnapshot<T> useFuture<T>(Future<T>? future, {T? initialData, bool preserveState = true});

T? useFutureData<T>(
Future<T>? future, {
T? initialData,
bool preserveState = true,
void Function(Object, StackTrace)? onError,
});
  • future - the Future to listen to. A null future leaves the snapshot in its initial state.
  • initialData (null by default) - the value to expose before the Future completes. When provided, the initial snapshot carries it with ConnectionState.none.
  • preserveState (true by default) - when the future instance changes, keep the last value during the gap before the new one resolves. Set it to false to reset to initialData instead.

useFutureData is a convenience variant that returns data directly instead of the whole snapshot. It pipes the snapshot through useAsyncSnapshotErrorHandler under the hood, so an emitted error is reported rather than silently swallowed; the optional onError chooses where it goes.

Use cases

  • Querying data from an API or database (however, take a look at useAutoComputedState which is usually better suited for loading screen data - it tracks initialization, guards null inputs, and exposes refresh).
  • Resolving a one-shot asynchronous value during a build, such as a file read or a plugin call.

Caveats

  • A new future instance restarts the subscription. Building the Future inline creates a fresh one on every build, so the work runs again each rebuild. Pair useFuture with useMemoized - or reach for useMemoizedFuture, which packages exactly that:

    Profile? useProfile(UserService service, String userId) {
    // Without useMemoized a fresh Future would be created on every build, so the
    // subscription would restart each rebuild.
    final request = useMemoized(() => service.loadProfile(userId), [userId]);
    return useFutureData(request); // <- Returns the value directly; reports errors itself
    }
  • An error from the Future lands in snapshot.error and stays there silently unless something reads it. Handle it explicitly via useAsyncSnapshotErrorHandler, or use useFutureData, which wires that handler for you.

  • initialData is read only once, on first build. Later changes to the value passed as initialData are ignored, exactly as with useState.

tip

For loading screen data you usually want useAutoComputedState rather than useFuture + useMemoized. Reach for useFuture when you need raw AsyncSnapshot semantics or are mirroring an existing FutureBuilder.

See also