Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat: adduseThrottled hooks.#471

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
ostk0069 wants to merge2 commits intorrousselGit:master
base:master
Choose a base branch
Loading
fromostk0069:feat/use-throttle

Conversation

ostk0069
Copy link

@ostk0069ostk0069 commentedMay 25, 2025
edited by coderabbitaibot
Loading

close.#472

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Introduced a new hook that allows throttling the execution of callbacks, helping to limit how frequently a callback can be triggered within a specified duration.
  • Tests

    • Added tests to verify that the new throttling hook correctly limits callback execution and behaves as expected during rapid user interactions.

yang-lile reacted with hooray emoji
@coderabbitaicoderabbitai
Copy link

coderabbitaibot commentedMay 25, 2025
edited
Loading

Walkthrough

A new throttling hook,useThrottled, has been added to the Flutter Hooks package. The hook is implemented in a new file, integrated into the main hooks library, and a dedicated test suite has been introduced to verify its throttling behavior and correctness through widget tests.

Changes

File(s)Change Summary
.../lib/src/hooks.dartAddedpart 'throttled.dart'; directive to include the new throttling hook source file.
.../lib/src/throttled.dartIntroduceduseThrottled hook and internal_Throttler class to provide throttled callback execution.
.../test/use_throttle_test.dartAdded new widget tests foruseThrottled, including a custom test widget and two test cases for throttling.

Sequence Diagram(s)

sequenceDiagram  participant Widget  participant useThrottled  participant _Throttler  Widget->>useThrottled: Initialize with duration  useThrottled->>_Throttler: Create throttler instance  loop On User Tap    Widget->>useThrottled: Call throttled callback    useThrottled->>_Throttler: run(callback)    alt Timer not active      _Throttler->>Widget: Execute callback      _Throttler->>_Throttler: Start timer for duration    else Timer active      _Throttler-->>Widget: Ignore callback    end    Note over _Throttler: After duration, timer resets  end
Loading

Poem

In the land of hooks, a throttler appears,
To slow down the taps and quiet our fears.
With timers and tests, it keeps things in line,
Only once per half-second—now isn’t that fine?
Fluttering softly, the state hops anew,
Throttle on, dear bunny, this one’s for you! 🐇⏱️


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat withCodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag@coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag@coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on oursupport page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings togenerate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add@coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add@coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add@coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a.coderabbit.yaml file to the root of your repository.
  • Please see theconfiguration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation:# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit ourDocumentation for detailed information on how to use CodeRabbit.
  • Join ourDiscord Community to get help, request features, and share feedback.
  • Follow us onX/Twitter for updates and announcements.

@ostk0069ostk0069 mentioned this pull requestMay 25, 2025
Copy link

@coderabbitaicoderabbitaibot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
packages/flutter_hooks/lib/src/throttled.dart (2)

3-3:Fix grammatical error in documentation.

The documentation comment contains a grammatical error.

-/// widget ignore updates accordingly after a specified [duration] duration.+/// Throttles widget updates to occur at most once per specified [duration].

7-12:Improve documentation example clarity.

The current example could be more clear about the throttling behavior and use a more typical throttling scenario.

-/// String userInput = ''; // Your input value-///-/// // Create a throttle callback-/// final throttle = useThrottle(duration: const Duration(milliseconds: 500));-/// // Assume a fetch method fetchData(String query) exists-/// Button(onPressed: () => throttle(() => fetchData(userInput)));+/// final searchQuery = useState('');+/// final throttle = useThrottle(duration: const Duration(milliseconds: 500));+///+/// TextField(+///   onChanged: (value) {+///     searchQuery.value = value;+///     // This will be throttled to run at most once every 500ms+///     throttle(() => performSearch(value));+///   },+/// );
packages/flutter_hooks/test/use_throttle_test.dart (2)

40-41:Remove redundant delay operations.

BothpumpAndSettle(_duration) andFuture.delayed(_duration) are unnecessary. ThepumpAndSettle should be sufficient to advance the timer.

-await tester.pumpAndSettle(_duration);-await Future<void>.delayed(_duration);+await Future<void>.delayed(_duration);+await tester.pump();

73-73:Consider adding test for custom duration.

The tests only cover the default duration. Consider adding a test case that verifies custom duration parameter works correctly.

Add a test case like:

+testWidgets('custom duration works correctly', (tester) async {+  // Test with a shorter duration like 100ms+  // to verify the duration parameter is respected+});

Would you like me to generate a complete test case for custom duration validation?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and betweene8215f0 and07b1e42.

📒 Files selected for processing (3)
  • packages/flutter_hooks/lib/src/hooks.dart (1 hunks)
  • packages/flutter_hooks/lib/src/throttled.dart (1 hunks)
  • packages/flutter_hooks/test/use_throttle_test.dart (1 hunks)
🔇 Additional comments (6)
packages/flutter_hooks/lib/src/hooks.dart (1)

40-40:LGTM! Proper integration of the new throttle hook.

The part directive correctly follows the established pattern and alphabetical ordering of other hook inclusions in the library.

packages/flutter_hooks/lib/src/throttled.dart (3)

14-19:LGTM! Clean function implementation.

TheuseThrottle function is well-implemented:

  • Proper use ofuseMemoized to maintain the throttler instance
  • Correct dependency array with[duration]
  • Clean return of the throttler's run method

21-26:LGTM! Proper validation in constructor.

The constructor correctly validates that the duration is positive and provides a clear assertion message.


34-41:LGTM! Correct throttling implementation.

The throttling logic is correctly implemented:

  • Executes callback immediately if no timer is running (leading edge behavior)
  • Starts timer to prevent subsequent executions during the duration
  • Properly resets the timer when duration completes

This follows the standard throttling pattern effectively.

packages/flutter_hooks/test/use_throttle_test.dart (2)

7-31:LGTM! Comprehensive throttling validation.

This test correctly validates that multiple rapid taps are throttled, ensuring only the first tap within the duration period triggers a state update.


52-71:LGTM! Well-structured test widget.

The test widget properly demonstrates theuseThrottle hook usage with state management and user interaction.

Comment on lines 33 to 48
testWidgets('update number after duration', (tester) async {
await tester.runAsync<void>(() async {
await tester.pumpWidget(const _UseThrottleTestWidget());

final text = find.byType(GestureDetector);
expect(find.text('1'), findsOneWidget);

await tester.pumpAndSettle(_duration);
await Future<void>.delayed(_duration);

await tester.tap(text);
await tester.pump();

expect(find.text('2'), findsOneWidget);
});
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

🛠️ Refactor suggestion

Improve test to better validate throttle reset behavior.

The current test doesn't fully validate that the throttle properly resets. It only tests a single tap after waiting, not that subsequent taps can occur after the throttle period.

 testWidgets('update number after duration', (tester) async {   await tester.runAsync<void>(() async {     await tester.pumpWidget(const _UseThrottleTestWidget());     final text = find.byType(GestureDetector);     expect(find.text('1'), findsOneWidget);+    // First tap should work immediately+    await tester.tap(text);+    await tester.pump();+    expect(find.text('2'), findsOneWidget);++    // Wait for throttle duration to pass     await tester.pumpAndSettle(_duration);     await Future<void>.delayed(_duration);+    // Next tap should work after throttle resets     await tester.tap(text);     await tester.pump();-    expect(find.text('2'), findsOneWidget);+    expect(find.text('3'), findsOneWidget);   }); });
🤖 Prompt for AI Agents
In packages/flutter_hooks/test/use_throttle_test.dart around lines 33 to 48, thetest only verifies a single tap after the throttle duration but does not confirmthat the throttle resets to allow subsequent taps. Modify the test to includemultiple taps separated by the throttle duration, verifying that the widgetupdates correctly each time, thereby ensuring the throttle reset behavior isproperly validated.

/// Button(onPressed: () => throttle(() => fetchData(userInput)));
/// ```
void Function(VoidCallback callback) useThrottle({
Duration duration = const Duration(milliseconds: 500),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This value seems arbitrary. I'd rather not have a default value and make the parameter required

ostk0069 reacted with thumbs up emoji
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

updated with4ebb7f7

/// // Assume a fetch method fetchData(String query) exists
/// Button(onPressed: () => throttle(() => fetchData(userInput)));
/// ```
void Function(VoidCallback callback) useThrottle({
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Should be nameduseThrottled to match `useDebounced

ostk0069 reacted with thumbs up emoji
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

updated with4ebb7f7

/// // Assume a fetch method fetchData(String query) exists
/// Button(onPressed: () => throttle(() => fetchData(userInput)));
/// ```
void Function(VoidCallback callback) useThrottle({
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I'm not sure about the API

I'd like something that matches theuseDebounced API, for consistency

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I thinkuseDebounced has big difference of usage.
useThrottled should be callback method to use.

Copy link

@coderabbitaicoderabbitaibot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Actionable comments posted: 3

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between07b1e42 and4ebb7f7.

📒 Files selected for processing (2)
  • packages/flutter_hooks/lib/src/throttled.dart (1 hunks)
  • packages/flutter_hooks/test/use_throttle_test.dart (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/flutter_hooks/test/use_throttle_test.dart
🔇 Additional comments (1)
packages/flutter_hooks/lib/src/throttled.dart (1)

14-19:LGTM! Function signature and implementation are well-structured.

The function correctly:

  • Makesduration required (addressing past feedback)
  • UsesuseMemoized with proper dependency tracking
  • Returns a clean callback interface

Comment on lines +3 to +13
/// widget ignore updates accordingly after a specified [duration] duration.
///
/// Example:
/// ```dart
/// String userInput = ''; // Your input value
///
/// // Create a throttle callback
/// final throttle = useThrottled(duration: const Duration(milliseconds: 500));
/// // Assume a fetch method fetchData(String query) exists
/// Button(onPressed: () => throttle(() => fetchData(userInput)));
/// ```

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

🛠️ Refactor suggestion

Improve documentation clarity and correctness.

The documentation has grammatical issues and could better explain the throttling behavior:

-/// widget ignore updates accordingly after a specified [duration] duration.+/// Creates a throttled callback that executes at most once per [duration].+///+/// The returned function will execute the provided callback immediately on the+/// first call, then ignore subsequent calls until the duration has elapsed.

Also consider adding more context about the throttling behavior (leading edge vs trailing edge) to set proper expectations.

📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// widget ignore updates accordingly after a specified[duration] duration.
///
/// Example:
/// ```dart
/// String userInput = ''; // Your input value
///
/// // Create a throttle callback
/// final throttle = useThrottled(duration: const Duration(milliseconds: 500));
/// // Assume a fetch method fetchData(String query) exists
/// Button(onPressed: () => throttle(() => fetchData(userInput)));
/// ```
/// Creates a throttled callback that executes at most once per[duration].
///
/// The returned function will execute the provided callback immediately on the
/// first call, then ignore subsequent calls until the duration has elapsed.
///
/// Example:
///
🤖 Prompt for AI Agents
In packages/flutter_hooks/lib/src/throttled.dart around lines 3 to 13, improvethe documentation by correcting grammatical errors and clarifying theexplanation of the throttling behavior. Add details about whether the throttletriggers on the leading edge, trailing edge, or both, to set proper expectationsfor users. Ensure the example is clear and accurately demonstrates how to usethe throttle callback.

Comment on lines +30 to +37
void run(VoidCallback callback) {
if (!_isRunning) {
_timer = Timer(duration, () {
_timer = null;
});
callback();
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

🛠️ Refactor suggestion

Consider edge case handling for invalid durations.

The current implementation doesn't handle edge cases likeDuration.zero or negative durations, which could lead to unexpected behavior.

 void run(VoidCallback callback) {+  if (duration <= Duration.zero) {+    callback();+    return;+  }+   if (!_isRunning) {     _timer = Timer(duration, () {       _timer = null;     });     callback();   } }

For zero or negative durations, the callback should execute immediately without throttling.

📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
voidrun(VoidCallback callback) {
if (!_isRunning) {
_timer=Timer(duration, () {
_timer=null;
});
callback();
}
}
voidrun(VoidCallback callback) {
if (duration<=Duration.zero) {
callback();
return;
}
if (!_isRunning) {
_timer=Timer(duration, () {
_timer=null;
});
callback();
}
}
🤖 Prompt for AI Agents
In packages/flutter_hooks/lib/src/throttled.dart around lines 30 to 37, the runmethod does not handle zero or negative Duration values, which can causeunexpected behavior. Modify the method to check if the duration is zero ornegative; if so, execute the callback immediately without setting a timer orthrottling. Otherwise, proceed with the existing throttling logic.

Comment on lines +21 to +38
class _Throttler {
_Throttler(this.duration);

final Duration duration;

Timer? _timer;

bool get _isRunning => _timer != null;

void run(VoidCallback callback) {
if (!_isRunning) {
_timer = Timer(duration, () {
_timer = null;
});
callback();
}
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

💡 Verification agent

🧩 Analysis chain

Verify timer resource cleanup behavior.

The current implementation should handle timer cleanup correctly, but it's worth verifying that timers are properly disposed when the widget is unmounted.


🏁 Script executed:

#!/bin/bash# Description: Check if there are examples of timer cleanup patterns in other hooks# Expected: Find similar timer usage patterns and cleanup mechanismsrg -A 10 -B 5"Timer" --type dart packages/flutter_hooks/lib/src/

Length of output: 2978


🏁 Script executed:

#!/bin/bash# Display _DebouncedHookState implementation and its dispose method if anyrg -A20 -B5"class _DebouncedHookState" --type dart packages/flutter_hooks/lib/src/debounced.dartecho"----Searching for dispose override in debounced.dart----"rg"@override\s+void\s+dispose" --type dart packages/flutter_hooks/lib/src/debounced.dart# Display _Throttler usage: look for HookState and disposerg -A20 -B5"class _ThrottledHookState" --type dart packages/flutter_hooks/lib/src/throttled.dart||echo"No HookState class in throttled.dart"echo"----Searching for dispose override in throttled.dart----"rg"@override\s+void\s+dispose" --type dart packages/flutter_hooks/lib/src/throttled.dart

Length of output: 1224


🏁 Script executed:

#!/bin/bash# Display full content of throttled.dartsed -n'1,200p' packages/flutter_hooks/lib/src/throttled.dart

Length of output: 972


Cleanup pending timers on unmount

The_Throttler inpackages/flutter_hooks/lib/src/throttled.dart schedules aTimer but never cancels it when the widget is disposed. If that timer fires after unmount, it may invoke callbacks on a disposed widget. To address this:

  • In_Throttler, add acancel() method:
    voidcancel() {  _timer?.cancel();  _timer=null;}
  • InuseThrottled, register a cleanup effect:
    final throttler=useMemoized(()=>_Throttler(duration), [duration]);useEffect(() {return () {    throttler.cancel();  };}, [throttler]);return throttler.run;
  • (Optional) Reviewpackages/flutter_hooks/lib/src/debounced.dart—it likewise never cancels its timer on dispose and should follow a similar pattern.

These changes ensure no stray timers fire after the hook is unmounted.

🤖 Prompt for AI Agents
In packages/flutter_hooks/lib/src/throttled.dart around lines 21 to 38, the_Throttler class creates a Timer but does not cancel it when the widgetunmounts, risking callbacks firing on disposed widgets. Fix this by adding acancel() method to _Throttler that cancels and nullifies the timer. Then, in theuseThrottled hook, register a cleanup effect that calls throttler.cancel() onunmount. This ensures timers are properly cleaned up and no callbacks run afterdisposal.

@itsMohammedAli
Copy link

Hi 👋,

Thanks for theuseThrottled hook — it’s a great addition!

I’d like to suggest enhancing it with support forthrottle modes (leading,trailing, andboth) to give developers more control over when the throttled callback fires. This is especially useful for UI scenarios where you want either immediate response or delayed trailing updates (or both).

Additionally, adding.cancel() and.flush() methods to the returned controller would be very helpful:

  • .cancel() to abort any pending throttled calls and clean up timers (important on widget disposal or navigation).
  • .flush() to immediately execute any pending throttled call (useful for finalizing input or scroll events).

These features are common in popular throttle/debounce utilities likeLodash, and including them would makeuseThrottled more powerful and flexible for real-world Flutter apps.

I’m happy to help contribute these improvements if the maintainers are open to it. Thanks!

@ostk0069ostk0069 changed the titlefeat: adduseuseThrottle hooks.feat: adduseThrottled hooks.Jul 1, 2025
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@coderabbitaicoderabbitai[bot]coderabbitai[bot] left review comments

@rrousselGitrrousselGitAwaiting requested review from rrousselGit

Assignees
No one assigned
Labels
None yet
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

3 participants
@ostk0069@itsMohammedAli@rrousselGit

[8]ページ先頭

©2009-2025 Movatter.jp