-
-
Notifications
You must be signed in to change notification settings - Fork 187
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 1 commit
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,42 @@ | ||||||||||||||||||||||||||||||||||||||||||||
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 = useThrottle(duration: const Duration(milliseconds: 500)); | ||||||||||||||||||||||||||||||||||||||||||||
/// // Assume a fetch method fetchData(String query) exists | ||||||||||||||||||||||||||||||||||||||||||||
/// Button(onPressed: () => throttle(() => fetchData(userInput))); | ||||||||||||||||||||||||||||||||||||||||||||
/// ``` | ||||||||||||||||||||||||||||||||||||||||||||
void Function(VoidCallback callback) useThrottle({ | ||||||||||||||||||||||||||||||||||||||||||||
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. I'm not sure about the API I'd like something that matches the 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. I think |
||||||||||||||||||||||||||||||||||||||||||||
Duration duration = const Duration(milliseconds: 500), | ||||||||||||||||||||||||||||||||||||||||||||
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. This value seems arbitrary. I'd rather not have a default value and make the parameter required 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. updated with 4ebb7f7 |
||||||||||||||||||||||||||||||||||||||||||||
}) { | ||||||||||||||||||||||||||||||||||||||||||||
final throttler = useMemoized(() => _Throttler(duration), [duration]); | ||||||||||||||||||||||||||||||||||||||||||||
return throttler.run; | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
class _Throttler { | ||||||||||||||||||||||||||||||||||||||||||||
_Throttler(this.duration) | ||||||||||||||||||||||||||||||||||||||||||||
: assert( | ||||||||||||||||||||||||||||||||||||||||||||
0 < duration.inMilliseconds, | ||||||||||||||||||||||||||||||||||||||||||||
'duration must be greater than 0ms', | ||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
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
|
||||||||||||||||||||||||||||||||||||||||||||
} |
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('useThrottle', () { | ||
testWidgets('no update when tapping multiple times', (tester) async { | ||
await tester.runAsync<void>(() async { | ||
await tester.pumpWidget(const _UseThrottleTestWidget()); | ||
|
||
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 _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); | ||
}); | ||
}); | ||
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 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
|
||
}); | ||
} | ||
|
||
class _UseThrottleTestWidget extends HookWidget { | ||
const _UseThrottleTestWidget(); | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
final textNumber = useState(1); | ||
final throttle = useThrottle(); | ||
|
||
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.
Should be named
useThrottled
to match `useDebouncedThere 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.
updated with 4ebb7f7