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.
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.