duhaime
duhaime

Reputation: 27594

React Native: Touchable Opacity element is clickable on iOS but not Android

I'm working on a React Native app with a typeahead component. The typeahead displays options that overlay other content on the route (see right image below). When a user clicks one of those options, an onPress listener runs a function:

enter image description here

This all works just fine on iOS. On Android though, the onPress event is never received. Even more strangely, when I try to click on an option lower in the list (like Boston, MA, USA), the onPress event is received by the card below the pressed option (Djerba).

Does anyone know what might cause this behavior? I'd be super grateful for any insights others can offer on this query.

Here's the code for the Explore view and the typeahead components.

Explore.js

import React from 'react'
import { connect } from 'react-redux'
import { Text, View, ScrollView, TouchableOpacity } from 'react-native'
import { gradients, sizing } from '../../style'
import { LinearGradient } from 'expo-linear-gradient'
import { MountainHero } from '../Heros'
import { CardRow } from '../Card'
import Loading from '../Loading'
import { setExploreSearch, onExploreTypeaheadClick } from '../../actions/locations'
import { Typeahead } from '../Typeahead'

const styles = {
  container: {
    flex: 1,
    flexDirection: 'column',
  },
  scrollView: {
    paddingBottom: sizing.margin,
  },
  loadingContainer: {
    position: 'absolute',
    display: 'flex',
    flexDirection: 'column',
    justifyContent: 'center',
    alignItems: 'center',
    zIndex: 100,
    elevation: 100,
    top: 53,
    width: '100%',
  },
  typeahead: {
    margin: sizing.margin,
    marginBottom: 0,
    width: sizing.screen.width - (2*sizing.margin),
    zIndex: 100,
    elevation: 100,
  }
}

const Explore = props => {
  const { authenticated: a, spotlight, loading } = props;
  let r = (a.recommendedLocations || []);
  if (!r || !spotlight) return null;
  // remove spotlight locations from the recommended locations
  const ids = spotlight.map(i => i.guid);
  const recommended = r.filter(i => ids.indexOf(i.guid) == -1);
  return (
    <LinearGradient style={styles.container} colors={gradients.teal}>
      <ScrollView contentContainerStyle={styles.scrollView}>
        {loading && (
          <View style={styles.loadingContainer}>
            <Loading />
          </View>
        )}
        <MountainHero text='Explore' />
        <Typeahead
          style={styles.typeahead}
          placeholder='Search Cities'
          value={props.exploreSearch}
          onChange={props.setExploreSearch}
          vals={props.exploreTypeahead}
          valKey={'place_id'}
          onTypeaheadClick={props.onExploreTypeaheadClick}
        />
        <CardRow
          text='Explore Places'
          cards={recommended}
          type='location' />
        <CardRow
          text='In the Spotlight'
          cards={spotlight}
          type='location' />
      </ScrollView>
    </LinearGradient>
  )
}

const mapStateToProps = state => ({
  authenticated: state.users.authenticated,
  spotlight: state.locations.spotlight,
  exploreSearch: state.locations.exploreSearch,
  exploreTypeahead: state.locations.exploreTypeahead,
  loading: state.locations.loading,
})

const mapDispatchToProps = dispatch => ({
  setExploreSearch: s => dispatch(setExploreSearch(s)),
  onExploreTypeaheadClick: val => dispatch(onExploreTypeaheadClick(val)),
})

export default connect(mapStateToProps, mapDispatchToProps)(Explore)

Typeahead.js

import React from 'react'
import { Text, View, TouchableOpacity } from 'react-native'
import { sizing, GradientInput } from '../style'

const styles = {
  container: {
    position: 'absolute',
    zIndex: 100,
    elevation: 100,
    height: 400,
    width: '100%',
  },
  input: {
    width: '100%',
    borderRadius: 0,
  },
  typeaheadContainer: {
    position: 'absolute',
    zIndex: 100,
    elevation: 100,
    top: 55,
    width: '100%',
  },
  typeaheadRow: {
    padding: 10,
    paddingTop: 12,
    paddingBottom: 12,
    borderWidth: 1,
    borderColor: '#eeeeee',
    backgroundColor: '#ffffff',
    marginBottom: -1,
  },
  typeaheadRowText: {
    fontSize: 15,
    fontFamily: 'open-sans',
    lineHeight: 20,
    backgroundColor: '#ffffff',
  },
}

export const Typeahead = props => {
  return (
    <View style={[props.container, props.style]}>
      <GradientInput style={styles.input}
        placeholder={props.placeholder}
        value={props.value}
        onChange={props.onChange} />
      <TypeaheadList vals={props.vals}
        valKey={props.valKey}
        onTypeaheadClick={props.onTypeaheadClick} />
    </View>
  )
}

export const TypeaheadList = props => {
  if (!props.vals) return null;
  return (
    <View style={styles.typeaheadContainer}>
      {props.vals.map(i => {
        let text = i.text;
        if (text.length > 31) text = text.substring(0,31) + '...';
        return (
          <TouchableOpacity activeOpacity={0.5} key={i[props.valKey]}
            style={styles.typeaheadRow}
            onPress={() => props.onTypeaheadClick(i[props.valKey])}>
            <Text numberOfLines={1} style={styles.typeaheadRowText}>{text}</Text>
          </TouchableOpacity>
        )
      })}
    </View>
  )
}

export default Typeahead

Upvotes: 0

Views: 2021

Answers (1)

Arseniy
Arseniy

Reputation: 51

Try to move Typeahead component below all CardRow components and set position:absolute for Typeahead. Probably on android - the latest view shadow all views before (I am not sure, but I think you have to try it for next discovering issue).

You should also remove position: absolute from all but one component. Working code:

Explore.js

import React from 'react'
import { connect } from 'react-redux'
import { Text, View, ScrollView, TouchableOpacity } from 'react-native'
import { gradients, sizing } from '../../style'
import { LinearGradient } from 'expo-linear-gradient'
import { MountainHero } from '../Heros'
import { CardRow } from '../Card'
import Loading from '../Loading'
import { setExploreSearch, onExploreTypeaheadClick } from '../../actions/locations'
import { Typeahead } from '../Typeahead'
const styles = {
  container: {
    flex: 1,
    flexDirection: 'column',
  },
  scrollView: {
    paddingBottom: sizing.margin,
  },
  loadingContainer: {
    position: 'absolute',
    display: 'flex',
    flexDirection: 'column',
    justifyContent: 'center',
    alignItems: 'center',
    zIndex: 1,
    elevation: 1,
    top: 53,
    width: '100%',
  },
  topCardRow: {
    paddingTop: sizing.margin + sizing.gradientInput.height,
  },
  typeahead: {
    margin: sizing.margin,
    marginBottom: 0,
    width: sizing.screen.width - (2*sizing.margin),
    zIndex: 1,
    elevation: 1,
    position: 'absolute',
    top: sizing.mountainHero.height,
    left: 0,
  }
}
const Explore = props => {
  const { authenticated: a, spotlight, loading } = props;
  let r = (a.recommendedLocations || []);
  if (!r || !spotlight) return null;
  // remove spotlight locations from the recommended locations
  const ids = spotlight.map(i => i.guid);
  const recommended = r.filter(i => ids.indexOf(i.guid) == -1);
  return (
    <LinearGradient style={styles.container} colors={gradients.teal}>
      <ScrollView contentContainerStyle={styles.scrollView}>
        {loading && (
          <View style={styles.loadingContainer}>
            <Loading />
          </View>
        )}
        <MountainHero text='Explore' />
        <CardRow
          style={styles.topCardRow}
          text='Explore Places'
          cards={recommended}
          type='location' />
        <CardRow
          text='In the Spotlight'
          cards={spotlight}
          type='location' />
        <Typeahead
          style={styles.typeahead}
          placeholder='Search Cities'
          value={props.exploreSearch}
          onChange={props.setExploreSearch}
          vals={props.exploreTypeahead}
          valKey={'place_id'}
          onTypeaheadClick={props.onExploreTypeaheadClick}
        />
      </ScrollView>
    </LinearGradient>
  )
}
const mapStateToProps = state => ({
  authenticated: state.users.authenticated,
  spotlight: state.locations.spotlight,
  exploreSearch: state.locations.exploreSearch,
  exploreTypeahead: state.locations.exploreTypeahead,
  loading: state.locations.loading,
})
const mapDispatchToProps = dispatch => ({
  setExploreSearch: s => dispatch(setExploreSearch(s)),
  onExploreTypeaheadClick: val => dispatch(onExploreTypeaheadClick(val)),
})
export default connect(mapStateToProps, mapDispatchToProps)(Explore)

Typeahead.js

import React from 'react'
import { Text, View, TouchableOpacity } from 'react-native'
import { sizing, GradientInput } from '../style'

const styles = {
  container: {
    zIndex: 1,
    elevation: 1,
    height: 400,
    width: '100%',
  },
  input: {
    width: '100%',
    borderRadius: 0,
  },
  typeaheadContainer: {
    zIndex: 1,
    elevation: 1,
    top: 0,
    width: '100%',
  },
  typeaheadRow: {
    padding: 10,
    paddingTop: 12,
    paddingBottom: 12,
    borderWidth: 1,
    borderColor: '#eeeeee',
    backgroundColor: '#ffffff',
    marginBottom: -1,
    zIndex: 1,
    elevation: 1,
  },
  typeaheadRowText: {
    fontSize: 15,
    fontFamily: 'open-sans',
    lineHeight: 20,
    backgroundColor: '#ffffff',
  },
}

export const Typeahead = props => {
  return (
    <View style={[props.container, props.style]}>
      <GradientInput style={styles.input}
        placeholder={props.placeholder}
        value={props.value}
        onChange={props.onChange} />
      <TypeaheadList vals={props.vals}
        valKey={props.valKey}
        onTypeaheadClick={props.onTypeaheadClick} />
    </View>
  )
}

export const TypeaheadList = props => {
  if (!props.vals) return null;
  return (
    <View style={styles.typeaheadContainer}>
      {props.vals.map(i => {
        let text = i.text;
        if (text.length > 31) text = text.substring(0,31) + '...';
        return (
          <TouchableOpacity activeOpacity={0.5} key={i[props.valKey]}
            style={styles.typeaheadRow}
            onPress={() => props.onTypeaheadClick(i[props.valKey])}>
            <Text numberOfLines={1} style={styles.typeaheadRowText}>{text}</Text>
          </TouchableOpacity>
        )
      })}
    </View>
  )
}

export default Typeahead

Upvotes: 1

Related Questions