Search code examples
reactjsreact-nativereact-hooks

UseEffect doesn't get called in a child component


I have a React Native app that has this screen called TasksScreen:

export default function TasksScreen() {
  const [loading, setLoading] = useState<boolean>(true);

  const colorScheme = useColorScheme() ?? "light";
  const theme = colorScheme === "dark" ? darkTheme : lightTheme;
  const themeColors = colorStyles[colorScheme];

  const onLoadingChanged = (loading: boolean) => setLoading(loading);

  if (loading) {
    return (
      <View style={theme.loaderContainer}>
        <ActivityIndicator size="large" color={themeColors.primary} />
      </View>
    );
  }

  const selectTask = (task: TaskEntity) => {
    router.push(`/task/${task.id}`);
  };

  return (
    <SafeAreaView style={theme.container}>
      <View
        style={{
          flexDirection: "row",
          justifyContent: "space-between",
          alignItems: "center",
          padding: 16,
          backgroundColor: themeColors.background,
        }}
      >
        <Text type="subtitle">Tasks:</Text>
        <TouchableOpacity style={theme.buttonPrimary} onPress={() => router.push('/task/create')}>
          <Text style={theme.buttonTextPrimary}>Create new task</Text>
        </TouchableOpacity>
      </View>
      <TaskList key="TabsTasklist" setLoading={onLoadingChanged} selectTask={selectTask} />
    </SafeAreaView>
  );
}

Which in turn uses this child component called TaskList:

const TaskList = ({
  setLoading,
  selectTask,
}: {
  setLoading: (loading: boolean) => void;
  selectTask: (task: TaskEntity) => void;
}) => {
  const [tasks, setTasks] = useState<TaskEntity[]>([]);

  const colorScheme = useColorScheme() ?? "light";
  const theme = colorScheme === "dark" ? darkTheme : lightTheme;
  const themeColors = colorStyles[colorScheme];

  useEffect(() => {
    console.log("Fetching tasks!");

    const loadTasks = async () => {
      try {
        const fetchedTasks = await getAllTasks();

        if (fetchedTasks.length === 0) {
          Alert.alert("Error", "Failed to fetch tasks.");
        }
        console.log("Tasks fetched: ", fetchedTasks);

        setTasks(fetchedTasks);
        setLoading(false);
      } catch (error) {
        Alert.alert("Error", "Failed to fetch tasks. Reason: " + error);
      }
    };

    loadTasks();
  });

  return (
    <FlatList
      data={tasks}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => (
        <TaskListCell entity={item} onPress={selectTask} />
      )}
      contentContainerStyle={theme.listContainer}
      ListEmptyComponent={
        <Text style={theme.emptyText}>No task available!</Text>
      }
    />
  );
};

The problem is, the useEffect code to fetch the data is not being called at all. However, when I moved the loading/setLoading state from TasksScreen to TaskList (ie. from the parent to child), it works just fine. Can anybody tell me what went wrong here?

Thank you.


Solution

  • export default function TasksScreen() {
      const [loading, setLoading] = useState<boolean>(true);
      // ...
      if (loading) {
        return (
          <View style={theme.loaderContainer}>
            <ActivityIndicator size="large" color={themeColors.primary} />
          </View>
        );
      }
      // ...
    }
    

    The loading state starts as true, and if it's true the only things you render are a View and an ActivityIndicator. TaskList is not rendered at all, so its effects can not run. If you want TaskList to be rendered, you must include it in your return statement.