react-nativeexpolistitemflatlist

React Native Expo FlatList onPress() doesnt work


So I am using Expo and react-navigation along with ListItem from react-native-elements to create a FlatList that renders the names of doctors and I want the user to be able to click on a "Know More" button inside the ListItem which then navigates to a new screen containing information about the doctor. I am able to render the FlatList and have added an Alert on clicking the "Know more" button for testing purposes but the weird bug I get is that I have a total of 8 ListItems and the Alert shows up only on the last (i.e. the 8th) ListItem and does not work on the other elements.

Here's all my code from FindDocScreen:

import React from "react";
import {
  View,
  Text,
  StyleSheet,
  StatusBar,
  FlatList,
  TouchableOpacity,
  Alert,
} from "react-native";

import MyHeader from "../components/MyHeader";

import { ListItem, Avatar } from "@rneui/themed";
import { LinearGradient } from "expo-linear-gradient";

import { RFValue } from "react-native-responsive-fontsize";

const list = [
  {
    name: "Dr. Ashok Mahato",
    avatar_url: "https://api.lorem.space/image/face",
    subtitle: "General Medicine",
    rating: 4.2,
  },
  {
    name: "Dr. Tapan Kumar",
    avatar_url: "https://api.lorem.space/image/face",
    subtitle: "Cardiologist",
    rating: 4.5,
  },
  {
    name: "Dr. Reyaz Ahmed",
    avatar_url: "https://api.lorem.space/image/face",
    subtitle: "Neurologist",
    rating: 3.9,
  },
  {
    name: "Dr. Poonam Singh",
    avatar_url: "https://api.lorem.space/image/face",
    subtitle: "Opthalmologist",
    rating: 4,
  },
  {
    name: "Dr. Nikhilesh Kundu",
    avatar_url: "https://api.lorem.space/image/face",
    subtitle: "Dermatologist",
    rating: 3.8,
  },
  {
    name: "Dr. Manoj Kumar",
    avatar_url: "https://api.lorem.space/image/face",
    subtitle: "Psychiatrist",
    rating: 4.1,
  },
  {
    name: "Dr. Abhik Chatterjee",
    avatar_url: "https://api.lorem.space/image/face",
    subtitle: "Surgeon",
    rating: 4.6,
  },
  {
    name: "Dr. Ranjan Kumar",
    avatar_url: "https://api.lorem.space/image/face",
    subtitle: "Gynecologist",
    rating: 3.8,
  },
];

magicWork = () => {
  
};

const keyExtractor = (item, index) => index.toString();

const renderItem = ({ item, i, navigation }) => {
  return (
    <ListItem
      key={i}
      linearGradientProps={{
        colors: ["#6b79e4", "#7380f0"],
        start: { x: 1, y: 0 },
        end: { x: 0.2, y: 0 },
      }}
      ViewComponent={LinearGradient}
      style={styles.doc}
      containerStyle={{ borderRadius: 20 }}
      bottomDivider
    >
      <Avatar
        rounded
        title={item.name[0]}
        source={item.avatar_url && { uri: item.avatar_url }}
      />
      <ListItem.Content>
        <ListItem.Title style={{ color: "white", fontWeight: "bold" }}>
          {item.name}
        </ListItem.Title>
        <ListItem.Subtitle style={{ color: "white" }}>
          {item.subtitle} - {item.rating} ★
        </ListItem.Subtitle>
      </ListItem.Content>
      <ListItem.Content right={true}>
        <TouchableOpacity
          style={styles.button}
          onPress={() => {
            //navigation.replace("Details", { data: item });
            Alert.alert("Click", "Clicked button");
          }}
        >
          <Text style={styles.buttonText}>Know More</Text>
        </TouchableOpacity>
      </ListItem.Content>
    </ListItem>
  );
};

export default class FindDocScreen extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        <StatusBar
          barStyle="dark-content"
          hidden={false}
          backgroundColor="#f8f8f8"
          translucent={true}
        />
        <MyHeader />
        <Text style={styles.text}>Top doctors ★</Text>
        <FlatList
          contentContainerStyle={{ paddingBottom: RFValue(60) }}
          keyExtractor={keyExtractor}
          data={list}
          renderItem={renderItem}
        />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#e9ecf7",
  },

  heading: {
    fontSize: RFValue(27),
    fontWeight: "bold",
    color: "#fff",
    alignSelf: "center",
  },

  text: {
    fontSize: RFValue(30),
    marginLeft: RFValue(15),
    marginTop: RFValue(20),
    color: "#000",
    fontWeight: "bold",
  },

  doc: {
    marginTop: RFValue(60),
    width: "88%",
    height: RFValue(18),
    alignSelf: "center",
  },

  button: {
    width: RFValue(85),
    height: RFValue(30),
    backgroundColor: "#e9ecf7",
    borderRadius: 50,
    alignSelf: "center",
    textAlign: "center",
    justifyContent: "center",
  },

  buttonText: {
    fontSize: RFValue(11),
    alignSelf: "center",
    color: "#000",
  },
});

Solution

  • The implementation of your renderItem function seems wrong. The signature of this function is documented in the documentation and is as follows:

    renderItem({ item, index, separators });

    There is no navigation object that is passed to this function. Furthermore, if you destructure properties from an object, then the defined names of the corresponding keys must match. It is called index not i. Thus, there might be an issue with the key property that you are passing to ListItem.

    Hence,

    const renderItem = ({ item, i, navigation })
    

    is invalid.

    We need to change it to the following.

    const renderItem = ({ item, index })
    

    Furthermore, it seems odd that your using a key prop and a keyExtractor at the same time. It might be worth a try to choose only one of these options.

    Navigation

    The navigation object will be passed to screens that are defined as components inside of a navigator. This is documented here in the official documentation.

    Thus, we have a few options here to make this object accessible to the renderItem function.

    If FindDocScreen is a screen defined in a navigator, then we can pass it to this function directly.

    renderItem={({item, index}) => renderItem(item, index, this.props.navigation)}
    

    You need to change the signature of your renderItem function to the following.

    const renderItem = (item, index, navigation ) => {
        ....
    }
    

    If FindDocScreen is not a screen in the navigator, then you can access the navigation object anyway.

    For react functional components, this is easy using the useNavigation hook. Since your component does not provide any special reason for being a class component, I would convert it to a functional component and would use this hook instead.

    export default function FindDocScreen(props) {
        const navigation = useNavigation();
        
        return (
          <View style={styles.container}>
            <StatusBar
              barStyle="dark-content"
              hidden={false}
              backgroundColor="#f8f8f8"
              translucent={true}
            />
            <MyHeader />
            <Text style={styles.text}>Top doctors ★</Text>
            <FlatList
              contentContainerStyle={{ paddingBottom: RFValue(60) }}
              keyExtractor={keyExtractor}
              data={list}
              renderItem={renderItem={({item, index}) => renderItem(item, index, navigation)}}
            />
          </View>
        );
      }
    }