无法在已卸载的组件上执行React状态更新。 =>每当我更改页面时,React Native的useEffect循环都会调用更多次吗?

时间:2020-09-09 10:37:37

标签: firebase react-native android-emulator react-native-android use-effect

堆栈为:

  • TypeScript
  • 反应原生(无人参与)
  • 反应本地Firebase程序包
  • Firebase
  • Firestore

我有一个应用程序,其中创建了2个钩子和一个用于上/下投票的组件。

第一个自定义钩子是通用的Firebase文档处理程序。 每次我更改页面或重新加载页面时,都会越来越多地调用此循环。

我有这个警告:

无法在已卸载的组件上执行React状态更新。这是无操作,但表示您的应用程序中发生内存泄漏。要修复,请取消使用useEffect清理功能中的所有订阅和异步任务。

const useDocument = (ref: DocumentReference, create = false): UseDocumentType => {
  const [documentData, setDocumentData] = useState<DocumentData>({})
  const [documentExists, setDocumentExists] = useState<'untested' | boolean>('untested')
  const [updating, setUpdating] = useState(false)

  // Listen to the user post data
  useEffect(() => {
    // Declare the function that will be used to subscribe
    const subscriber = (): void => {
      try {
        ref?.onSnapshot((documentSnapshot: DocumentSnapshot) => {
          if (documentSnapshot && documentSnapshot.exists) {
            setUpdating(false)
            setDocumentExists(true)
            const data = documentSnapshot.data() || {}
            data.id = documentSnapshot.id
            setDocumentData(data)
            // The screen is covered by the above console.log, while not infinite
            console.log(`DEBUG > useDocument > subscriber > document ${ref.path} data fetched`)
          } else if (create) {
            // Create the document with a single field
            updateDocument({ createdOn: new Date() })
            console.log(`DEBUG > useDocument > subscriber > document ${ref.path} created by subscriber`)
          } else {
            setDocumentExists(false)
            console.log(`WARN > useDocument > subscriber > the document ${ref.path || '<unknown ref>'} does not exists in Firebase`)
          }
        })
      } catch (error) {
        throw functionError(error, 'subscriber', '', 'useDocument')
      }
    }

    subscriber()
    // Stop listening for updates when no longer required
    return (): void => subscriber()

    // Disabling ESLint rule since passing refs in hooks dependencies create infinite rerenders
    // Refs are functions and shall be wrapped into useCallback to avoid that.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [create, shallExist])

  // Function used to update documents
  const updateDocument = async (data: Partial<DocumentData>): Promise<void> => {
    try {
      setUpdating(true)
      await ref?.set(data, { merge: true })
      // This function can also create the document
      setDocumentExists(true)
    } catch (error) {
      throw functionError(error, 'updateDocument', data, 'useDocument')
    }
  }

  return { documentExists, documentData, updating, setUpdating, updateDocument }
}

然后由useFeedback自定义钩子调用:

const useFeedback = (user: AuthUser, postId: string): UseFeedbackType => {
  // State
  const [upvoted, setUpvoted] = useState<boolean>(false)
  const [downvoted, setDownvoted] = useState<boolean>(false)
  // Reference to the document handling user feedback for this post
  // Shall it be passed into a useCallback hook?
  const ref: DocumentReference<DocumentData> = userPostDoc(user, postId)
  // useDocument provides tooling necessary to work with the document
  const { documentExists, documentData, updating, setUpdating, updateDocument, toggleDocumentField, withTransaction } = useDocument(ref)

  // Update the state when the document data change
  useEffect(() => {
    console.log(`DEBUG > useFeedback > useEffect > liked=${documentData?.upvoted}, muted=${documentData?.downvoted}`)
    if (user) {
      setUpvoted(documentData?.upvoted || false)
      setMuted(documentData?.downvoted|| false)
    }
  }, [documentData, user])

  const onUpvote = async (): Promise<void> => {
    if (upvoted) {
      await updateDocument({ upvoted: false })
      setUpvoted(false)
    } else {
      await updateDocument({ upvoted: true})
      setUpvoted(true)
    }
  }

  const onMute = async (): Promise<void> => {
    if (muted) {
      await updateDocument({ downvoted: false })
      setMuted(false)
    } else {
      await updateDocument({ downvoted: true })
      setMuted(true)
    }
  }

  return { onUpvote, onMute, upvoted, muted, updating }
}

然后将该组件调用到屏幕中:

const PostScreen: FC<Props> = ({ route, navigation }: Props) => {
  const initialPostData = route?.params?.post
  //imported: const postDoc = (postId: string) => postsCollection.doc(postId)
  const ref = postDoc(initialPostData.id)
  const [postData, setPostData] = useState(initialPostData)
  const { documentData, documentExists } = useDocument(ref)

  // Force re-render
  useEffect(() => {
    if (documentExists === true) setPostData(documentData)
    // This console.log is also printed several time but "only" ~10 times
    console.log(
      `DEBUG > PostScreen > useEffect > documentExists=${documentExists} and postData=${objectToString(postData || initialPostData)}`,
    )
  }, [documentData, documentExists])

  return (
    <SafeAreaView style={styles.container}>
      <StatusBar backgroundColor="transparent" barStyle="dark-content" translucent={true} />
      <ScrollView>
        <FavoriteIcon data={postData} style={styles.bookmark} color={colors.white} size={50} />
        <View style={styles.contentContainer}>
          <Text style={styles.title}>{postData?.title}</Text>
          <Text style={styles.description}>{postData?.description || ''}</Text>
          <FeedbackIcons postId={postData?.id} />
        </View>
      </ScrollView>
    </SafeAreaView>
  )
}

我是React的新手。这实际上可行,但更新时间很长(尽管Firebase足够聪明,可以在上/下投票时使用它的缓存,但设置起来需要2秒),并且useEffect的日志记录很长,使我认为存在严重的优化问题...

感谢您的耐心和帮助!

编辑

重新启动Android模拟器后,每次我上/下一篇文章时,只有1个调用useFeedback和3个useDocument。

保存了几次代码后,我现在有10个对useFeedback的调用,每个之间有1到4个对useDocument的调用...并且每次我保存代码或更改页面时,它都会变得越来越糟。

0 个答案:

没有答案