react-router-native-stack icon indicating copy to clipboard operation
react-router-native-stack copied to clipboard

"Double tap" causes the animation to run twice and double rendering

Open alexborton opened this issue 6 years ago • 3 comments

Some people have fast fingers and some have slower devices.

I have noticed that if i tap a navigation link twice (before the animation has started), the animation runs twice, rendering the next route twice and "double stacking" the views. Obviously this is an issue... but i should imagine it would be a reasonably easy fix with an isAnimating hook to stop further requests after initial instruction?

alexborton avatar May 21 '18 09:05 alexborton

Yes this is an issue. A fix could be to pass an isAnimating prop to the child components of the Route. I'd be open to any PRs implementing a fix!

A work around could be to make your links only tappable once, perhaps with some local state. Since in the current architecture of the Stack transitions triggered by a link aren't cancellable, and the stack will always unmount the previous route component and re-mount a new route component on every transition, that 'only tappable once' state would get re-set after every navigation action.

Traviskn avatar Aug 18 '18 20:08 Traviskn

This issue also happen with react-navigation, I guess disable link before pushing is answer.

Yet a quick hotfix can be monkey patching history.push & pop and denounce them for 100ms & re enable after interactionManager signal end of animation

alzalabany avatar Oct 12 '18 00:10 alzalabany

We created an Animation context that can inform touchable elements when the stack navigator is animating so as to block onPress events for specific components:


// Create context
const AnimationContext = React.createContext({
  stackNavIsAnimating: false,
  handleNavIsAnimating: null,
})

export interface State {
  stackNavIsAnimating: boolean
}

export class AnimationProvider extends React.Component<{}, State> {
  state = {
    stackNavIsAnimating: false,
  }

  public handleNavIsAnimating = (isAnimating: boolean): void => {
    this.setState({ stackNavIsAnimating: isAnimating })
  }

  public render() {
    const { stackNavIsAnimating } = this.state

    return (
      <AnimationContext.Provider
        value={{
          stackNavIsAnimating,
          handleNavIsAnimating: this.handleNavIsAnimating,
        }}
      >
        {this.props.children}
      </AnimationContext.Provider>
    )
  }
}

// Capturing Stack navigation state
<AnimationContext.Consumer>
  {({ handleNavIsAnimating, stackNavIsAnimating }) => {
   return (
    <Stack isAnimating={handleNavIsAnimating}>
   )
  }}
</AnimationContext.Consumer>

// Disable touchable element when stack nav is animating
<AnimationContext.Consumer>
  {({ stackNavIsAnimating }) => (
    <BackButton
      onPress={stackNavIsAnimating ? noop : history.goBack}
    />
  )}
</AnimationContext.Consumer>

or as a hook:

// Make hook
export const useAnimationContext = () => useContext(AnimationContext)

// Example usage
const { stackNavIsAnimating } = useAnimationContext()

<TouchableOpacity onPress={!stackNavIsAnimating ? () => goBack() : noop} />

petekp avatar Jul 29 '19 22:07 petekp