Invalid hook call React Native FlatList Navigation

52 views Asked by At

I'm making a notes app in React Native and trying to make it so I can click on a note in a FlatList to edit it. I'm using react-router-native for this. I get an Error when clicking on any FlatList item. I know that this error has been asked on stack overflow before but the answers are all for class components, whereas I'm using functional components.

Error: Invalid hook call. Hooks can only be called inside of the body of a function component. 
import { FlatList, Pressable, StyleSheet, View } from "react-native"
import { useNavigate } from "react-router-native"
import theme from "../theme"
import Text from "./Text"

const styles = StyleSheet.create({
  separator: {
    height: 10,
    backgroundColor: theme.colors.background,
  },
  item: {
    padding: 8,
    backgroundColor: "white",
  },
})
const ItemSeparator = () => <View style={styles.separator} />

const renderItem = ({ item }) => (
  <View style={styles.item}>
    <Pressable onPress={() => useNavigate(`/${item.id}`)}>
      <Text fontWeight="bold" fontSize="subheading">
        {item.title}
      </Text>
      <Text>{item.body}</Text>
    </Pressable>
  </View>
)

const NoteList = ({ notes }) => {
  return (
    <FlatList
      data={notes}
      ItemSeparatorComponent={ItemSeparator}
      renderItem={renderItem}
      keyExtractor={(item) => item.id}
    />
  )
}
1

There are 1 answers

4
Drew Reese On BEST ANSWER

useNavigate is a React hook and can only be called by a React function component or other custom React hook. It cannot be called in nested functions/callbacks.

Move the useNavigate hook call to the NoteList component and refactor the renderItem callback to curry a passed navigate function.

const ItemSeparator = () => <View style={styles.separator} />;

const renderItem = (navigate) => ({ item }) => (
  <View style={styles.item}>
    <Pressable onPress={() => navigate(`/${item.id}`)}>
      <Text fontWeight="bold" fontSize="subheading">
        {item.title}
      </Text>
      <Text>{item.body}</Text>
    </Pressable>
  </View>
);

const NoteList = ({ notes }) => {
  const navigate = useNavigate(); // <-- hook called in React function

  return (
    <FlatList
      data={notes}
      ItemSeparatorComponent={ItemSeparator}
      renderItem={renderItem(navigate)} // <-- pass navigate
      keyExtractor={(item) => item.id}
    />
  );
};

Alternatively you could move the renderItem function declaration into the NoteList component so the navigate function is just closed over in callback scope.

const ItemSeparator = () => <View style={styles.separator} />;

const NoteList = ({ notes }) => {
  const navigate = useNavigate();

  const renderItem = ({ item }) => (
    <View style={styles.item}>
      <Pressable onPress={() => navigate(`/${item.id}`)}>
        <Text fontWeight="bold" fontSize="subheading">
          {item.title}
        </Text>
        <Text>{item.body}</Text>
      </Pressable>
    </View>
  );

  return (
    <FlatList
      data={notes}
      ItemSeparatorComponent={ItemSeparator}
      renderItem={renderItem}
      keyExtractor={(item) => item.id}
    />
  );
};