react-native-modal/react-native-modal

The modal emits a "flash" when used with useNativeDriver=true

Versus2017 opened this issue Β· 61 comments

When reporting a bug, please be sure to check if the issue still persists with react-native original modal:

Under the hood react-native-modal uses react-native original Modal component.
Before reporting a bug, try swapping react-native-modal with react-native original Modal component to check if the problem persists.

When reporting a bug, please be sure to include the following:

  • The outcome of the react-native-modal swap described above
  • A descriptive title
  • An isolated way to reproduce the behavior (example: GitHub repository with code isolated to the issue that anyone can clone to observe the problem)
  • What version of react-native-modal you're using, and the platform(s) you're running it on (iOS, Android, device)
  • What packages or other dependencies you're using
  • The behavior you expect to see, and the actual behavior

When you open an issue for a feature request, please add as much detail as possible:

  • A descriptive title
  • A description of the problem you're trying to solve, including why you think this is a problem
  • An overview of the suggested solution
  • If the feature changes current behavior, reasons why your solution is better

Please note by far the quickest way to get a new feature is to file a Pull Request.

I found that react-native-modal didn't clear all the content view when you want to close it. it will apparently show a flash view when you use useNativeDriver, finally I found it related to Modal class of react-native.

Here is the error:

My code just like this:

<Modal
    backdropColor={'black'}
    backdropOpacity={0.5}
    useNativeDriver={true}
  >
    <View style={styles.taskListWrapper}>
      <TaskListPage
        style={styles.taskList}
      />
    </View>
</Modal>

Obviously I used useNativeDriver, and the children view is TaskListPage view, it is a complex view, so react-native need to spend a lot of time to render it, then you may see a flash appeared when you close your modal. as far as I see the more complex of the children view the more frequently you may see the flash after closing.

My analysis:

I've made a lot of test, and finally i found that it related to the Modal of react-native in index.js file. although its animationType is none, but you can still see the disappear of the modal. So it will has a flash, I tried use slide animationType, and it obviously shows more specifics of what i saw, you guys can have a try.

That's what i think , I still try to find a better solution of the problem for now.
Hoping that you guys can give me some useful infomation, Thanks!

Hey @Versus2017 , thanks for submitting the issue.
It's is still not totally clear to me what kind of "flash" you're experiencing.

I don't know if it can be useful to you but I'm using this modal in some applications with huge lists and heavy renders and my approach is almost always to hide the content of the Modal before setting its visibility to false/true.

Regarding your suggestion: unfortunately currenly using the React-Native built in Modal is the only way for showing your content on top of the screen (without using it at root level).

Yeah, thanks for replying, I just noticed that we can only use Modal to show our content on the top of the screen.
I will have a try about what you said. thanks so much~

@Versus2017 I have the same problem you’ve described. As you’ve probably found, the flash goes away if the native driver is not used. The content of my modals isn’t very complex, though, so using the JS thread for animation isn’t an issue for me.

With debug builds, using the JS thread makes the animation seem very laggy w/ all the debugging tools running. On a release build, though, it’s very smooth.

I'm experiencing this as well and can confirm that the issue goes away if I set useNativeDriver={false}.

Basically after onModalHide is invoked there is a quick flash where the modal comes back for a tick and then it is gone again.

@mmerickel yes, you and me have the same problem now, and if I set useNativeDriver={false} , my animation seems not so smooth sometimes. so I need use useNativeDriver.

I just tried what the author said that hide the content of the Modal before setting its visibility to false. It did solved my problem, the quick flash almost disappear (sometimes it still shows, but it better than before), but the bad news is the closing animation is gone. the content just disappeared directly without any animationOut animation

@mmazzarolo I think your idea is the solution. but why can't we do that in your react-native-modal code. I just tried in your code. it's perfectly worked and without any flash again, and the animationOut is still exist.

I just add a state prop showContent just like you said, I need hide the content of the Modal before setting its visibility to false, So I use showContent prop to hide the content of the Modal after closing animation finished in _close function in index.js file. Can we do that ?

here is what I do : https://github.com/Versus2017/react-native-modal

Thanks everyone for the discussion!
I wasn't aware of the issue of the useNativeDriver={true}... which is something I'm not totally in control of since it is related to react-native-animatable.

but why can't we do that in your react-native-modal code
We can do it! I simply never thought it could have been an useful feature.
The only issue I see on adding it is that it might lead to further props/customizations that would make the modal even more complex to use for newcomers (e.g.: a props that controls the kind of animation used for showing/hiding the content only).

What's your opinion on the subject guys? @Versus2017 @mmerickel @wkoutre

On the one hand, I think your concern about that is right, it may actually confuses newcomers, and most of users may never meet the problem which happend to me. On the other hand, It is necessary for developers who need more higher performance of animation, and has more complex content with heavy render, just like me. It is big problem for them.

Here is our logic:

  • we need more higher performance for animation, so we need to set useNativeDriver to true.
  • and next, we meet the problem with a quick flash after closing animation finished as I said before.
  • and then, we analyze the problem, we know that it related to Modal of react-native
  • finally, we think we should clear the content of Modal after closing animation finished.

Here is our question :

Is it necessary to add this new feature in react-native-modal ?
I think it is necessay to add this new feature to react-native-modal, cause it will improve its performance of animation and i don't see any side effects in code, but not in this way of what i did in my own code. The improvement should be more clearly and easy to understand and use.

For example:

  • We don't need to expose it to users, and we bind it to useNativeDriver prop inside of the code. So if they use useNativeDriver={true}, we just open it to clear content view of Modal after closing animation finshed.

I agree with @Versus2017 when he writes:

We don't need to expose it to users, and we bind it to useNativeDriver prop inside of the code. So if they use useNativeDriver={true}, we just open it to clear content view of Modal after closing animation finshed.

I don't see how it would create any side effects if this is the standard, implicit functionality when using useNativeDrive={true}.

I don't see how it would create any side effects if this is the standard, implicit functionality when using useNativeDrive={true}.

The issue I can see is that this solution would hide the content of the modal for the entire duration of the animations. This could lead to nasty visual effects, especially if the user sets an high animation timing.

I think you should have a try with useNativeDriver={true} if you don't exactly know where the problem is. And I take some tests with react-native-modal examples with useNativeDriver={true}, and it hardly to show the quick flash, but when I add some other components to the content to make it a little more complex, it actually shows the quick flash after you tapped the close button.

The problem which I want to fix is:

  1. I set useNativeDriver={true}, because I need more smooth animation for my heavy render content
  2. It will shows a quick flash of content when isVisible changed to false.

The Code is here:

_close = () => {
  if (this.transitionLock) return;
  this.transitionLock = true;
  this.backdropRef.transitionTo({ opacity: 0 }, this.props.backdropTransitionOutTiming);
  this.contentRef[this.animationOut](this.props.animationOutTiming).then(() => {
    this.transitionLock = false;
    if (this.props.isVisible) {
      this._open();
    }
    else {

      // It will shows a quick flash when you set isVisible to false, 
      // the quick flash is Modal of react-native disappear flash

      this.setState({ isVisible: false })
      this.props.onModalHide();
    }
  });
};

return (
  <Modal
    transparent={true}
    animationType={'none'}
    visible={this.state.isVisible}
    onRequestClose={onBackButtonPress}
    {...otherProps}
  >

I guess the reason why we set useNativeDriver to true and the Modal will shows a quick flash when visible is false is useNativeDriver has some effects on Modal. So it will shows the quick animation of Modal disappear though the animationType is 'none', finally it becomes the quick flash of what you saw.

What happens when changing:

this.setState({ isVisible: false })
this.props.onModalHide();

to

this.setState({ isVisible: false }, this.props.onModalHide)

I guess my question here is: Are you certain the flash happens when isVisible is set to false? I think I remember coming to the conclusion that the flash actually happens when onModalHide is called β€” when I was first testing, it always occurred right after the amount of ms I set the hiding animation to be.

The flash does not always happen, but here is an illustration of the problem. I think it's probably a react-native problem with Animation.

dec -14-2017 20-49-25

@RichardLindhout I test it with a high animation timing, I found it happen when animation finished, so I don't think that it is react-native problem. you also can have a try, the problem you can see is modal disappear without animation

Hmm.. I still think it's a react-native problem since it's a difference in result when using the nativeDriver.
It's just a prop to the Animated object in React Native.

https://facebook.github.io/react-native/docs/animated.html

But there's no doubt that there will be a work-around to fix this nasty effect. If I have some spare time I'll dig in to it.

Same issue: oblador/react-native-animatable#148

Seems to be a problem which can be tracked down to react-native-animatable and probably react-native.

@RichardLindhout Have you tried adding this.props.onModalHide to the callback of setting state's isVisible: false, replacing the current synchronous implementation?

I could not reproduce this flash bug when making a simple modal with react-native and the native driver.

import React, { Component } from 'react'

import {
  View,
  Dimensions,
  Platform,
  // StatusBar,
  Text,
  Linking,
  Animated,
  TouchableOpacity,
  Easing,
  TouchableWithoutFeedback,
} from 'react-native'

import Gradient from './Gradient'

class Start extends Component {
  constructor(props) {
    super(props)

    this.state = {
      hide: false,
    }
  }
  componentWillMount() {
    this._visibility = new Animated.Value(this.state.hide ? 1 : 0)
  }

  _toggle = () => {
    const animation = Animated.timing(this._visibility, {
      toValue: this.state.hide ? 0 : 1,
      duration: 300,
      useNativeDriver: true,
      ease: Easing.bezier(0.25, 0.1, 0.25, 1),
    })

    if (this.state.hide) {
      this.setState(
        {
          hide: !this.state.hide,
        },
        () => animation.start()
      )
    } else {
      animation.start(() =>
        this.setState({
          hide: !this.state.hide,
        })
      )
    }
  }
  render() {
    const translateY = this._visibility.interpolate({
      inputRange: [0, 1],
      outputRange: [0, -Dimensions.get('window').height],
    })

    const containerStyle = {
      flex: 1,

      alignItems: 'center',
      justifyContent: 'center',

      transform: [
        {
          translateY,
        },
      ],
    }

    return (
      <View style={{ backgroundColor: 'blue', flex: 1 }}>
        <TouchableOpacity
          style={{ backgroundColor: 'red', width: 120, height: 80 }}
          onPress={() => alert('content touchable working')}
        />
        <View
          style={{
            //backdrop
            backgroundColor: this.state.hide
              ? 'rgba(0,0,0,0)'
              : 'rgba(0,0,0,0.5)',
            position: 'absolute',
            top: 0,
            bottom: 0,
            left: 0,
            right: 0,
            width: null,
            height: null,
          }}
          pointerEvents={this.state.hide ? 'box-none' : 'auto'}
        >
          <Animated.View style={containerStyle} pointerEvents="box-none">
            {!this.state.hide && (
              <View
                style={{
                  backgroundColor: 'white',
                  flexDirection: 'column',

                  borderRadius: 70,
                  height: 400,
                  width: 300,
                }}
              >
                <View
                  style={{
                    backgroundColor: 'green',
                    width: 20,
                    height: 40,
                  }}
                />

                <TouchableOpacity
                  style={{ backgroundColor: 'pink', width: 120, height: 80 }}
                  onPress={() => alert('touchable working')}
                />

                <View
                  style={{
                    backgroundColor: 'green',
                    width: 20,
                    height: 40,
                    flex: 1,
                  }}
                />
              </View>
            )}
          </Animated.View>
        </View>

        <TouchableOpacity
          onPress={this._toggle}
          style={{
            position: 'absolute',
            bottom: 0,
            right: 0,
            left: 0,
            width: null,
            backgroundColor: '#fff',
            padding: 10,
          }}
        >
          <Text>Test modal hide/show</Text>
        </TouchableOpacity>
      </View>
    )
  }
}

export default Start


@RichardLindhout I agree with you about that maybe it is react-native animated problem. but I have fix this issue in my own code, I removed the content after animation, so I think it is not the main problem about react-native Animated animation. here is my code https://github.com/Versus2017/react-native-modal. There will be no more flash again.

@Versus2017 Thanks for your repository. We wil probably switch branches.

@Versus2017 @RichardLindhout @wkoutre sorry guys but I've been super busy lately, I hope to take a closer inspection at the issue during my xmas holiday.
In the meanwhile, thanks for your contributions :)

@Versus2017 I keep getting the same flash with your repository

@RichardLindhout I don't get the flash any more with my repository for now, maybe I am still not solve the problem, and maybe it's actually related to React-Native animation, I will continue to follow this problem. Thanks for your feedback.

@Versus2017 You're right, I used https://github.com/mmazzarolo/react-native-modal-datetime-picker and this library has another dependency so I'm probably going to fork that one ;)

@RichardLindhout I'm glad it's work. you are welcome!

Issue recap:

The issue:
Using the native driver on the modal (useNativeDrive={true}) introduces a nasty "flash" while animating it. We still haven't discovered what is causing it.

The fix:
One fix that I proposed for this issue is hiding the entire content of the modal while it is animated, and it has been implemented in this branch. The PR has not been merged yet because hiding the content of the modal is not a nice practice and it might introduce ugly side effects on existing projects.
What should we do then? We have two main path that we can follow:

  • Implement the fix by adding a prop (eg.: hideModalContentWhileAnimating) and/or by tying enabling it only when useNativeDrive={true}
  • Dig deeper to find the real issue underneath the flash

Am I right? What do you think?

Also, thank you all guys for contributing (@Versus2017 @RichardLindhout @wkoutre ), and have a nice new year πŸŽ‰

@mmazzarolo Thanks, The summary is exactly right, and I think we should dig deeper about this issue, and I will try to find out why this happened when I am free ( i'm busy these days), and Thanks for all. Happy 2018~

I have the same issue

+1

For everyone who upvoted the thread, did you had a chance to try the fix proposed above?
Did it work for you?
Thanks!

I'm also using the hiding content trick, but instead of hiding it directly before the animation start, I use a setTimeout instead.
Let's say if the animationOutTiming is 300 then I would set a timeout of 200 to hide the content.
It's working good so far, but I haven't tested on many devices yet.

Edit: It's still flashing LOL just less than before

+1

For everyone who upvoted the thread, did you had a chance to try the fix proposed above?
Did it work for you?
Thanks!

@mmazzarolo I tested the proposed fix from @Versus2017 in our project and I haven't seen the flash yet πŸŽ‰ It seems to be working really well! And modal transitions are way smoother with useNativeDriver={true} 😸

@Versus2017 @mmazzarolo the fix works for me too. I just opened a pull request #116 to summary the fix of your proposal.

Thanks @alex-blair and @peteroid !
I just merged @peteroid PR, could anyone else give it a test too so that I can release it on npm?
Once it has been released I'll update the README.md describing the issue in detail and stating that this is a workaround (we still need to find the real source of the issue).

Thanks guys πŸ‘

Dumb question probably : How can we test it if it is not on npm ?
Also, What about using a beta tag ?

Dumb question probably : How can we test it if it is not on npm ?

Just install it directly from the master branch npm i -S git@github.com:react-native-community/react-native-modal.git

Also, What about using a beta tag ?

Sure! I'll do it this evening (I'm at work right now)

Released as beta: npm i -S react-native-modal@5.1.0-0.
Feedbacks are warmly welcomes 🎈

I confirm that #116 fixes the flash isssue on iOS for me.

Unfortunately, i still have the flash issue on iOS 😞

Unfortunately, i still have the flash issue on iOS too. But I found the frequency of flash is decreased.

Did you set hideModalContentWhileAnimating?

@mmazzarolo I think nope. Should I set it?

It does work now with hideModalContentWhileAnimating
My bad. Awesome πŸ’―

@mmazzarolo May I ask how to use it? It doesn't listed on document. Do I just add hideModalContentWhileAnimating with useNativeDriver for the Modal component right?

@wellyshen sure!
Setting the new hideModalContentWhileAnimating prop of the modal to true should be enough!

I'm adding it to the README.md too πŸ‘

Let me know if it works for you.

I also updated the README by adding a FAQ and the prop description.

@mmazzarolo Thanks man, It works.

Nice!
Closed, feel free to open another issue if needed

If anybody is experiencing similar flicking issue on Android adding the renderToHardwareTextureAndroid prop helped me.
hideModalContentWhileAnimating or useNativeDriver didn't - FYI I'm using react-native-dialog.

Issue Fixed
<Dialog.Container
    visible={this.state.dialogVisible}
    onBackButtonPress={this.toggleDialog}
    renderToHardwareTextureAndroid>
    <Dialog.Title>title</Dialog.Title>
    <Text>blah</Text>
    <Dialog.Button label="OK" onPress={this.toggleDialog} />
</Dialog.Container>

Don't know what i am doing wrong, but the flickering exists when using "slideInDown" opening and "slideOutUp" closing animations. Here are my props

<Modal swipeDirection="up" animationIn="slideInDown" animationOut="slideOutUp" isVisible={this.state.visible} backdropTransitionOutTiming={0} swipeThreshold={50} backdropOpacity={0.2} supportedOrientations={['portrait', 'landscape']} onSwipeComplete={this.swipeCompletedHandler} onSwipeStart={this.swipeStartedHandler} onSwipeCancel={this.swipeCancelHandler} hideModalContentWhileAnimating={true} useNativeDriver={true} >

Using useNativeDriver={false} also didn't help.

Hello there i've used a trick to "fix" this problem, you just have to pass the property backdropTransitionOutTiming to 0 like the following

            <Modal
                isVisible={modalInput.isModalVisible}
                avoidKeyboard
                useNativeDriver={Platform.OS === "android"}
                hideModalContentWhileAnimating
                backdropTransitionOutTiming={0}  // <- the trick
                backdropOpacity={1}
                animationIn="slideInUp"
                animationOut="slideOutDown"
                />
sntg-p commented

Hello there i've used a trick to "fix" this problem, you just have to pass the property backdropTransitionOutTiming to 0 like the following

            <Modal
                isVisible={modalInput.isModalVisible}
                avoidKeyboard
                useNativeDriver={Platform.OS === "android"}
                hideModalContentWhileAnimating
                backdropTransitionOutTiming={0}  // <- the trick
                backdropOpacity={1}
                animationIn="slideInUp"
                animationOut="slideOutDown"
                />

useNativeDriverForBackdrop={true} is better if you don't want to lose the background fade in and fade out animations.