React native onPress with TouchableOpacity combined with elevation show ugly shadow

Viewed 2307

Using RN 0.63.4 and trying to implement a touchable card in Android environment.

The code is pretty simple:

<TouchableOpacity onPress={() => handlePress()}>
    <ArticleContainer>
        ....
    </ArticleContainer>
</TouchableOpacity>

This is the CSS for the ArticleContainer

const ArticleContainer = styled.View`
    height: 170px;
    width: 320px;
    border-radius: 11px;
    padding: 13px;
    margin: 20px 15px 10px 20px;
    elevation: 5;
    background-color: white;
`;

The UI looks fine when not touched, even the elevation looks pretty good:

enter image description here

But when trying to touch it, it becomes so ugly, look at the weird shadows and border:

enter image description here

Found only a few answers in StackOverflow and one in the official GitHub which both didn't work for me.

Is there is a way to solve that?

5 Answers

Solution:

I recently faced similar issue. What worked for me was to apply shadows on TouchableOpacity instead of the View inside it.

What was causing the issue?

Not sure about it, but I think that when the shadows were applied on View, the TouchableOpacity will drop the opacity of its child i.e View on being pressed, which somehow did not effect its shadows, leaving them exposed.

That being said I am really not sure about why applying shadows to TouchableOpacity worked.

Other possible cause/solution

  • backgroundColor - In many cases not setting a background color on the component with shadows, causes shadows to not behave properly. Just set it to transparent if you don't want any background color.

Note:

The solution worked for me but I am not sure if it is the best solution or even if it will work for you as the behavior might be different depending on the platform and also if you are using expo.

  • I personally was NOT using expo and was testing my application on Android

Edit:

I tried to reproduce the issue and apply the solution on expo. Below images shows TouchableOpacity in a pressed state.

Shadow on child View Shadow on TouchableOpacity

I've faced the exact same problem. Just found that it was caused by the elevation in the style. Removed it and solved.

Edit

elevation + TouchableOpacity = ugly.

Found a better solution. I use TouchableNativeFeedback for Android, and TouchableOpacity for iOS.

With TouchableNativeFeedback, you need to set background and useForeground props. Note that you also need to set overflow: hidden to the inner view. More details here.

Here's my card view:

interface Props extends TouchableWithoutFeedbackProps {
  style?: ViewStyle;
  children?: React.ReactNode;
}

export default function CardView({style, children, onPress}: Props) {
  const card = (
    <View
      style={{
        shadowColor: 'gery',
        backgroundColor: 'white',
        padding: 18,
        overflow: Platform.OS === 'android' ? 'hidden' : 'visible',

        // iOS
        shadowOffset: {width: 0, height: 2},
        shadowRadius: 18,
        shadowOpacity: 0.2,
        borderRadius: 12,

        // Android
        elevation: 8,

        ...style,
      }}>
      {children}
    </View>
  );

  if (Platform.OS === 'android') {
    return (
      <TouchableNativeFeedback
        onPress={e => onPress?.call(undefined, e)}
        background={TouchableNativeFeedback.Ripple('#00000040', false)}
        useForeground={true}>
        {card}
      </TouchableNativeFeedback>
    );
  } else {
    return (
      <TouchableOpacity
        onPress={e => onPress?.call(undefined, e)}
        activeOpacity={0.5}>
        {card}
      </TouchableOpacity>
    );
  }
}

Add the styles directly to TouchableOpacity

Instead of this

<TouchableOpacity onPress={() => handlePress()}>
    <View style={your_styles}>
       ....
    </View >
</TouchableOpacity>

Add Styles directly to TouchableOpacity

<TouchableOpacity onPress={() => handlePress()} style={your_styles}>
    
       ....
   
</TouchableOpacity>

You can either use the activeOpacity prop of TouchableOpacity (the default is 0.2) and Add backfaceVisibility: 'hidden' to your ArticleContainer style. Or you can use touchablewithoutfeedback, that should do the trick.

<TouchableOpacity style={styles.buttonContainer} >

         <Text style={styles.buttonText}>Text</Text>

</TouchableOpacity>

style:

buttonContainer: {
        elevation: 10,
        width: '100%',
        borderRadius: 12,
        elevation: 10,
        marginVertical: 10,
        backgroundColor: '#ffffff',
        alignItems: 'center',
    }, 
    buttonText: {
        lineHeight: 50,
        color: "#000000",
    },

enter image description here

enter image description here

Thank You.

Related