Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using useFuture and useStream keeps invoking the widget build method unnecessarily. #418

Closed
computervirus91 opened this issue Mar 3, 2024 · 5 comments
Assignees
Labels
bug Something isn't working question Further information is requested

Comments

@computervirus91
Copy link

Steps,

  1. Extend your widget with HookWidget as usual.
  2. Put useFuture or useStream inside the build method.
  3. Check the builds with a print statement.

You will notice that the widget build method is invoked several times a second without any updates from Future or Stream.

@davidmartos96
Copy link
Contributor

davidmartos96 commented Mar 3, 2024

Sounds like you are recreating the Future/Stream in every build. What you need to do is cache it between builds, with a useMemoized for example

final future = useMemoized(() => api.fetch(), []);
final snap = useFuture(future);
...

@rrousselGit rrousselGit added question Further information is requested and removed needs triage labels Mar 3, 2024
@rrousselGit
Copy link
Owner

Please share a complete example. Otherwise I can only assume that this is your mistake

@computervirus91
Copy link
Author

// useFuture example
class PageOne extends HookWidget {
  const PageOne({super.key});

  @override
  Widget build(BuildContext context) {
    final snapshot = useFuture<String>(Future.delayed(
      const Duration(milliseconds: 500),
      () => 'complete',
    ));
    print('################# use future build');
    return Scaffold(
      appBar: AppBar(title: const Text('use future')),
      body: Center(child: Text(snapshot.data ?? 'in progress')),
    );
  }
}

// useStream example
Stream<int> generateRange(int start, int finish) async* {
  for (int i = start; i <= finish; i++) {
    yield await Future.delayed(const Duration(milliseconds: 500), () => i);
  }
}

class PageTwo extends HookWidget {
  final int start;
  final int finish;

  const PageTwo({
    super.key,
    required this.start,
    required this.finish,
  });

  @override
  Widget build(BuildContext context) {
    final snapshot = useStream<int>(generateRange(start, finish));
    print('################# use stream build');
    return Scaffold(
      appBar: AppBar(title: const Text('use stream')),
      body: Center(child: Text(snapshot.data.toString())),
    );
  }
}

@rrousselGit
Copy link
Owner

Indeed, like mentioned by David above, you need to cache your Future/Stream in a useMemoized if you want to do that.

If the future/stream passed to useFuture/useStream changes, they start listening to it. So you re-enter a "loading" cycle. It's not a hooks bug :)

@computervirus91
Copy link
Author

This is nonintuitive. Also, useStream documentation doesn't mention/recommend using useMemoized.

This can create unexpected bugs and/or suboptimal performance for users. For me, using useMemoized with useFuture/useStream is not an acceptable solution.

Anyways, thank you for your response.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working question Further information is requested
Projects
None yet
Development

No branches or pull requests

3 participants