V
V
Vladimir2022-04-07 14:43:18
React
Vladimir, 2022-04-07 14:43:18

Can useEfffect indirectly call itself if it changes the state it is bound to?

There is a small React Native utility made in Expo, and it has a component that lists text files in the program directory and gives you the option to delete the file or send it by mail:

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>
  );
};


The main idea was that the list of files is updated only when the fileResponse state changes, and this state changes only after one of the files is deleted (well, when the component is mounted). In practice, it turned out that the readFiles function is called constantly while the component is on the screen (file extensions are sent to the console in a continuous stream). Do I understand correctly that since fileResponce changes inside useEffect, then the component is re-rendered and useEffect is called again, and so on ad infinitum?

If yes, how to avoid it? You can, of course, make a separate state as a flag that you need to reread the list of files, but is it possible to somehow do without it?

Answer the question

In order to leave comments, you need to log in

1 answer(s)
I
ikutin666, 2022-04-07
@ikutin666

least

useEffect(() => {
    let isMounted = true;
    readFiles().then(data => { if (isMounted) setFileResponse(data) });
    return () => { isMounted = false };
  }, [fileResponse])

at least you define isMounted and it will always be true and after changing fileResponse useEffect will run again, and there will be no situation where isMounted=false

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question