Задать вопрос
@AstraVlad
Финансист, консультант, программист-любитель

Может ли useEfffect косвенно вызывать сам себя если меняет стейт, на который он завязан?

Есть небольшая утилита на 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 и так до бесконечности?

Если да, то как этого избежать? Можно, конечно, сделать отдельный стейт в качестве флага что нужно перечитать список файлов, но можно ли как-то обойтись без него?
  • Вопрос задан
  • 51 просмотр
Подписаться 1 Простой Комментировать
Пригласить эксперта
Ответы на вопрос 1
@ikutin666
как минимум
useEffect(() => {
    let isMounted = true;
    readFiles().then(data => { if (isMounted) setFileResponse(data) });
    return () => { isMounted = false };
  }, [fileResponse])


как минимум вы определяете isMounted и он всегда будет true и после изменения fileResponse useEffect запуститься еще раз, и не будет ситуации где isMounted=false
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы