-
-
Notifications
You must be signed in to change notification settings - Fork 186
feat: add useThrottled
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,38 @@ | ||||||||||||||||||||||||||||||||||||||||||||
part of 'hooks.dart'; | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
/// 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))); | ||||||||||||||||||||||||||||||||||||||||||||
/// ``` | ||||||||||||||||||||||||||||||||||||||||||||
void Function(VoidCallback callback) useThrottled({ | ||||||||||||||||||||||||||||||||||||||||||||
required Duration duration, | ||||||||||||||||||||||||||||||||||||||||||||
}) { | ||||||||||||||||||||||||||||||||||||||||||||
final throttler = useMemoized(() => _Throttler(duration), [duration]); | ||||||||||||||||||||||||||||||||||||||||||||
return throttler.run; | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
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(); | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+30
to
+37
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 like 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
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+21
to
+38
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainVerify 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 mechanisms
rg -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 any
rg -A20 -B5 "class _DebouncedHookState" --type dart packages/flutter_hooks/lib/src/debounced.dart
echo "----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 dispose
rg -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.dart
sed -n '1,200p' packages/flutter_hooks/lib/src/throttled.dart Length of output: 972 Cleanup pending timers on unmount The
These changes ensure no stray timers fire after the hook is unmounted. 🤖 Prompt for AI Agents
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:flutter_hooks/flutter_hooks.dart'; | ||
import 'package:flutter_test/flutter_test.dart'; | ||
|
||
void main() { | ||
group('useThrottled', () { | ||
testWidgets('no update when tapping multiple times', (tester) async { | ||
await tester.runAsync<void>(() async { | ||
await tester.pumpWidget(const _UseThrottledTestWidget()); | ||
|
||
final text = find.byType(GestureDetector); | ||
expect(find.text('1'), findsOneWidget); | ||
|
||
await tester.tap(text); | ||
await tester.pump(); | ||
|
||
expect(find.text('2'), findsOneWidget); | ||
|
||
await tester.tap(text); | ||
await tester.pump(); | ||
expect(find.text('2'), findsOneWidget); | ||
|
||
await tester.tap(text); | ||
await tester.pump(); | ||
expect(find.text('2'), findsOneWidget); | ||
|
||
await tester.tap(text); | ||
await tester.pump(); | ||
expect(find.text('2'), findsOneWidget); | ||
}); | ||
}); | ||
|
||
testWidgets('update number after duration', (tester) async { | ||
await tester.runAsync<void>(() async { | ||
await tester.pumpWidget(const _UseThrottledTestWidget()); | ||
|
||
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); | ||
}); | ||
}); | ||
}); | ||
} | ||
|
||
class _UseThrottledTestWidget extends HookWidget { | ||
const _UseThrottledTestWidget(); | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
final textNumber = useState(1); | ||
final throttle = useThrottled(duration: _duration); | ||
|
||
void updateText() { | ||
textNumber.value++; | ||
} | ||
|
||
return MaterialApp( | ||
home: GestureDetector( | ||
onTap: () => throttle(updateText), | ||
child: Text(textNumber.value.toString()), | ||
), | ||
); | ||
} | ||
} | ||
|
||
const _duration = Duration(milliseconds: 500); |
There was a problem hiding this comment.
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:
Also consider adding more context about the throttling behavior (leading edge vs trailing edge) to set proper expectations.
📝 Committable suggestion
🤖 Prompt for AI Agents