Есть небольшая утилита на React Native, сделанная в Expo, и в ней есть компонент, который выводит список текстовых файлов в каталоге программы и даёт возможность удалить файл или отправить его по почте:
function SendSurveyScreen({ route, navigation }) {
const { surveyerName, otherParam } = route.params
const [fileResponse, setFileResponse] = useState([]);
const [selectedId, setSelectedId] = useState(null);
const [sName, setSName] = useState(surveyerName)
const Item = ({ item, onPress, backgroundColor, textColor }) => (
<TouchableOpacity onPress={onPress} style={[styles.item, backgroundColor]}>
<Text style={[styles.title, textColor]}>{item.title}</Text>
</TouchableOpacity>
);
const renderItem = ({ item }) => {
const backgroundColor = item.id === selectedId ? "#6e3b6e" : 'lightblue';
const color = item.id === selectedId ? 'white' : 'black';
return (
<Item
item={item}
onPress={() => setSelectedId(item.id)}
backgroundColor={{ backgroundColor }}
textColor={{ color }}
/>
);
};
const readFiles = async () => {
try {
return (await FileSystem.readDirectoryAsync(FileSystem.documentDirectory))
.map((value, index) => {
console.log(value.slice(-4))
return value.slice(-4) == '.txt' ? { id: index, title: value } : null
});
} catch (err) {
console.warn(err);
}
}
useEffect(() => {
let isMounted = true;
readFiles().then(data => { if (isMounted) setFileResponse(data) });
return () => { isMounted = false };
}, [fileResponse])
//Получаем имя файла по номеру в списке
const getFileByID = (files, id) => {
const retFile = files.find(file => file.id == id)
return retFile.title
}
...
return (
<SafeAreaView style={styles.container}>
<FlatList
data={fileResponse}
renderItem={renderItem}
keyExtractor={(item) => item.id}
extraData={selectedId}
/>
<Text></Text>
<Button
title='Отправить файл по электронной почте'
disabled={selectedId == null}
onPress={() => {
emailFile(getFileByID(fileResponse, selectedId))
}}
/>
<Button
title='Удалить файл'
disabled={selectedId == null}
onPress={() => {
Alert.alert(
"Вы уверены?",
"Вы точно хотите удалить данные? Эту операцию нельзя отменить.",
[
{ text: "Нет, оставим", style: 'cancel', onPress: () => { } },
{
text: "Да, удалить",
style: 'destructive',
onPress: () => {
FileSystem.deleteAsync(FileSystem.documentDirectory + getFileByID(fileResponse, selectedId))
.then(value => console.log(value))
.catch(e => console.log(e))
setFileResponse([])
setSelectedId(null)
},
},
]
);
}
}
/>
<Button
title='На главный экран'
onPress={() => navigation.navigate('Старт')}
/>
</SafeAreaView>
);
};
Основная идея была в том, что список файлов обновляется только при изменении стейта fileResponse, а этот стейт меняется только после удаления одного из файлов (ну и при монтировании компонента). На практике выяснилось, что функция readFiles вызывается постоянно пока компонент на экране (в консоль идут расширения файлов непрерывным потоком). Правильно ли я понимаю, что раз внутри useEffect меняется fileResponce, то происходит повторный рендеринг компонента и повторный вызов useEffect и так до бесконечности?
Если да, то как этого избежать? Можно, конечно, сделать отдельный стейт в качестве флага что нужно перечитать список файлов, но можно ли как-то обойтись без него?