- 
          
 - 
                Notifications
    
You must be signed in to change notification settings  - Fork 2.2k
 
feat: add debounce functionality to TouchableRipple #4828
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: main
Are you sure you want to change the base?
Conversation
- Add optional debounce prop to prevent rapid successive presses - Implement debounce logic in both web and native TouchableRipple components - Add comprehensive test interface in TouchableRipple example - Update documentation with usage examples - Maintain backward compatibility with existing code
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.
This PR is being reviewed by Cursor Bugbot
Details
Your team is on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle for each member of your team.
To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.
| expect(onPress).toHaveBeenCalledTimes(2); | ||
| 
               | 
          ||
| jest.useRealTimers(); | ||
| }); | 
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.
Bug: Unmocked Time Improves Debounce Testing Reliability
The debounce test uses jest.useFakeTimers() and jest.advanceTimersByTime(), but the debounce implementation relies on Date.now(). Since Date.now() isn't mocked by Jest's fake timers by default, advanceTimersByTime() doesn't affect the debounce logic's internal clock. This makes the test unreliable and unable to accurately verify the debounce behavior.
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.
Pull Request Overview
This PR adds a debounce feature to the TouchableRipple component to prevent rapid successive presses. The debounce functionality uses a time-based approach that ignores press events occurring within a specified time window after the first press.
- Adds optional 
debounceprop (in milliseconds) to TouchableRipple component - Implements debouncing logic using 
Date.now()and a ref to track last press time - Includes comprehensive test coverage for debounced and non-debounced behavior
 
Reviewed Changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description | 
|---|---|
| src/components/TouchableRipple/TouchableRipple.tsx | Added debounce prop, implementation logic, and type annotations for PressableStateCallbackType | 
| src/components/TouchableRipple/TouchableRipple.native.tsx | Added debounce prop and implementation logic for native platform | 
| src/components/tests/TouchableRipple.test.tsx | Added tests for debounce functionality with fake timers | 
| example/src/Examples/TouchableRippleExample.tsx | Enhanced example with interactive debounce demonstration | 
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| expect(onPress).toHaveBeenCalledTimes(1); | ||
| 
               | 
          ||
| // Fast forward time past debounce window | ||
| jest.advanceTimersByTime(400); | 
    
      
    
      Copilot
AI
    
    
    
      Nov 2, 2025 
    
  
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.
The test advances timers by 400ms, but the debounce implementation uses Date.now() which is not affected by jest.advanceTimersByTime(). This test will likely fail because Date.now() returns real time, not fake timer time. Mock Date.now() using jest.spyOn(Date, 'now') and control its return value, or use jest.setSystemTime() if using modern fake timers.
| onPressOut={handlePressOut} | ||
| disabled={disabled} | ||
| style={(state) => [ | ||
| style={(state: PressableStateCallbackType) => [ | 
    
      
    
      Copilot
AI
    
    
    
      Nov 2, 2025 
    
  
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.
[nitpick] Adding explicit type annotation here is unnecessary since TypeScript can infer the type from the Pressable component's props. This change adds no value and increases verbosity. Consider removing the type annotation unless there's a specific type inference issue.
| ]} | ||
| > | ||
| {(state) => | ||
| {(state: PressableStateCallbackType) => | 
    
      
    
      Copilot
AI
    
    
    
      Nov 2, 2025 
    
  
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.
[nitpick] Adding explicit type annotation here is unnecessary since TypeScript can infer the type from the Pressable component's children render prop. This change adds no value and increases verbosity. Consider removing the type annotation unless there's a specific type inference issue.
| {(state: PressableStateCallbackType) => | |
| {state => | 
| onPress={debouncedOnPress} | ||
| > | ||
| {({ pressed }) => ( | ||
| {({ pressed }: { pressed: boolean }) => ( | 
    
      
    
      Copilot
AI
    
    
    
      Nov 2, 2025 
    
  
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.
[nitpick] Adding inline type annotation for a destructured parameter is unnecessarily verbose. TypeScript can infer this type from the Pressable component's children render prop. Consider removing the type annotation unless there's a specific type inference issue.
| {({ pressed }: { pressed: boolean }) => ( | |
| {({ pressed }) => ( | 
| 
           Hey @BohdanKhv, thank you for your pull request 🤗. The documentation from this branch can be viewed here.  | 
    
Motivation
Had to write extra code just to implement this in my apps
Note
Add optional debounce to
TouchableRipple(web/native), wire it toonPress, and update examples and tests.debounceprop toTouchableRipple(web:src/components/TouchableRipple/TouchableRipple.tsx, native:src/components/TouchableRipple/TouchableRipple.native.tsx).debouncedOnPressusingDate.now()+ ref; hook intoPressableviaonPress={debouncedOnPress}.PressableStateCallbackTypeon web.example/src/Examples/TouchableRippleExample.tsxwith a scrollable demo showcasing basic ripple and a debounce test (normal vs debounced buttons, counters, reset).src/components/__tests__/TouchableRipple.test.tsxto cover debounced vs non-debounced presses and existing behaviors.Written by Cursor Bugbot for commit c77ad85. This will update automatically on new commits. Configure here.