javascriptreact-nativereact-hooksreact-navigation

React "Invalid Hook Call" error when I use useNavigation


Photo of Error Message

I created a Product Card in React, and in the product card, I want the user to be able to press it and navigate to another Product Detail Screen. Now, every time I initialize the useNavigation, it throws me a "Invalid hook call."

import { TouchableOpacity, Image, StyleSheet, Text, View } from 'react-native'
import React from 'react'
import { useNavigation } from '@react-navigation/native';

const ProductCard = ({ item }) => {
  const navigation = useNavigation(); // This Line Throws the Error

  return (
    <TouchableOpacity
      onPress={() => {
        navigation.navigate('Product_Details', { item });
      }}
      style={styles.container}
    >
      <Image
        source={{ uri: item.image }}
        style={styles.coverImage}
        onError={(error) => console.log('Image load error:', error)}
      />
      <View style={styles.content}>
        <Text style={styles.brand}>{item.brand}</Text>
        <Text style={styles.title}>{item.title}</Text>
        <Text style={styles.price}>${item.price}</Text>
      </View>
    </TouchableOpacity>
  );
};

export default ProductCard;

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: 10,
    margin: 5,
    borderWidth: 1,
    borderColor: "#e8e8e8",
  },
  coverImage: {
    height: 256,
    width: "100%",
  },
  brand: {
    fontSize: 16,
    color: "#444444",
    fontWeight: "700",
  },
  price: {
    fontSize: 18,
    color: "#9C9C9C",
    fontWeight: "600",
  },
  content: {
    padding: 15,
  },
  title: {
    fontSize: 14,
    color: "#444444",
    fontWeight: "450"
  }
});

I have it entered in my function, I even tried creating a separate function inside my ProductCard, and outside my ProductCard and back calling, but I get the same error every time. Any help would be appreciated!

I call ProductCard in my homepage, see code below

const HomeScreen = () => {
  const [products, setProducts] = useState(data.products);
  const [selectedCategory, setSelectedCategory] = useState(null);

  return (
    // Creating a propertly to show which category is selected
    <ScrollView
      contentContainerStyle={styles.container}>
      {/* Product List */}
      <FlatList
        ListHeaderComponent={
          <>
            {/* Adding the categories bar to home page */}
            <FlatList
              style={styles.catStyle}
              data={categories}
              renderItem={({ item }) => (
                <Category
                  item={item}
                  selectedCategory={selectedCategory}
                  setSelectedCategory={setSelectedCategory}
                />
              )}
              keyExtractor={(item) => item}
              horizontal={true}
              showsHorizontalScrollIndicator={false}
            />
          </>
        }
        data={products}
        renderItem={ProductCard}
        numColumns={2}
        contentContainerStyle={{
          paddingBottom: 25,
        }}
      />
    </ScrollView>
  );
};

Solution

  • It seems the renderItem prop consumes a function that returns JSX.

    Flatlist::renderItem

    renderItem({
      item: ItemT,
      index: number,
      separators: {
        highlight: () => void;
        unhighlight: () => void;
        updateProps: (select: 'leading' | 'trailing', newProps: any) => void;
      }
    }): JSX.Element;
    

    Now while your ProductCard component is technically a function that consumes an object argument that matches the renderItem function signature and returns JSX, there are special rules regarding what is a React function and where and how React hooks can be called. React components are passed as JSX to React and React handles calling the function. The renderItem prop is calling your ProductCard function directly.

    Update the code to be a function that returns ProductCard JSX. This would be similar to what you did already with the Category component.

    renderItem={(props) => <ProductCard {...props} />}
    

    or

    renderItem={({ item }) => <ProductCard item={item} />}