Use useEffectEvent for effects with deliberately narrow deps
`pnpm lint` runs oxlint with `--quiet`, which hides warnings, so 14 react-hooks/exhaustive-deps warnings were sitting unseen. Five are the shape useEffectEvent exists for: an effect that must read the latest value of something without re-running when it changes. - CustomFeedEmptyState, FindContactsSettings: fire-and-forget metrics that omitted `ax` from the dep array - AutosizedTextarea: the `onUpdateHeight` callback prop - SuggestedLanguage: replaces a `useNonReactiveObject` mirror, which was doing the same job by hand - VideoEmbedInnerWeb: the hls subtitle handler called a captured `updateCuePositions`, so it would have gone stale if that identity changed Warnings: 14 -> 9. The rest are useMemo/useCallback/useImperativeHandle, where useEffectEvent does not apply. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,11 @@
|
||||
import {useCallback, useEffect, useId, useRef, useState} from 'react'
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useEffectEvent,
|
||||
useId,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {useLingui} from '@lingui/react/macro'
|
||||
import type * as HlsTypes from 'hls.js'
|
||||
@@ -264,6 +271,14 @@ function useHLS({
|
||||
},
|
||||
)
|
||||
|
||||
/*
|
||||
* The hls handler below must call the latest `updateCuePositions` without the
|
||||
* effect tearing down and re-attaching every time its identity changes.
|
||||
*/
|
||||
const onSubtitleFragProcessed = useEffectEvent(() => {
|
||||
updateCuePositions()
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (!videoRef.current) return
|
||||
if (!Hls) return
|
||||
@@ -302,7 +317,7 @@ function useHLS({
|
||||
})
|
||||
|
||||
hls.on(Hls.Events.SUBTITLE_FRAG_PROCESSED, () => {
|
||||
updateCuePositions()
|
||||
onSubtitleFragProcessed()
|
||||
})
|
||||
|
||||
hls.on(Hls.Events.FRAG_BUFFERED, (_event, {frag}) => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {useEffect, useMemo, useRef, useState} from 'react'
|
||||
import {useEffect, useEffectEvent, useMemo, useRef, useState} from 'react'
|
||||
import {
|
||||
TextInput,
|
||||
type TextInputContentSizeChangeEvent,
|
||||
@@ -141,13 +141,17 @@ export function AutosizedTextarea({
|
||||
* Reset native height state after a programmatic clear. Android uses it as
|
||||
* the explicit input height, while iOS uses it to decide when to scroll.
|
||||
*/
|
||||
const reportHeight = useEffectEvent((height: number) => {
|
||||
onUpdateHeight?.(height)
|
||||
})
|
||||
|
||||
const prevRawValue = useRef(rawValue || '')
|
||||
useEffect(() => {
|
||||
if (!IS_NATIVE) return
|
||||
if (rawValue === undefined) return // uncontrolled
|
||||
if (prevRawValue.current?.length && rawValue === '') {
|
||||
setNativeHeight(minInputHeight)
|
||||
onUpdateHeight?.(minInputHeight)
|
||||
reportHeight(minInputHeight)
|
||||
}
|
||||
prevRawValue.current = rawValue
|
||||
}, [rawValue, minInputHeight])
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {useCallback, useEffect, useState} from 'react'
|
||||
import {useCallback, useEffect, useEffectEvent, useState} from 'react'
|
||||
import {type ListRenderItemInfo, View} from 'react-native'
|
||||
import * as Contacts from 'expo-contacts'
|
||||
import {type DidString} from '@atproto/syntax'
|
||||
@@ -61,12 +61,17 @@ export function FindContactsSettingsScreen({}: Props) {
|
||||
const {data, error, refetch} = useContactsSyncStatusQuery()
|
||||
|
||||
const isFocused = useIsFocused()
|
||||
|
||||
const logPresented = useEffectEvent(() => {
|
||||
ax.metric('contacts:settings:presented', {
|
||||
hasPreviouslySynced: !!data?.syncStatus,
|
||||
matchCount: data?.syncStatus?.matchesCount,
|
||||
})
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (data && isFocused) {
|
||||
ax.metric('contacts:settings:presented', {
|
||||
hasPreviouslySynced: !!data.syncStatus,
|
||||
matchCount: data.syncStatus?.matchesCount,
|
||||
})
|
||||
logPresented()
|
||||
}
|
||||
}, [data, isFocused])
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {useEffect, useMemo, useRef, useState} from 'react'
|
||||
import {useEffect, useEffectEvent, useMemo, useRef, useState} from 'react'
|
||||
import {Platform, Text as RNText, View} from 'react-native'
|
||||
import {parseLanguageString} from '@atproto/syntax'
|
||||
import {
|
||||
@@ -332,15 +332,18 @@ function GuessedLanguage({
|
||||
onDeclineOuter()
|
||||
}
|
||||
|
||||
const metaRef = useNonReactiveObject(metadata)
|
||||
useEffect(() => {
|
||||
const logSuggestion = useEffectEvent(() => {
|
||||
ax.metric('composer:language:suggestLanguage', {
|
||||
os: Platform.OS,
|
||||
suggestedLanguage: language,
|
||||
currentTargetLanguages: metaRef.current.currentTargetLanguages,
|
||||
currentTargetLanguages: metadata.currentTargetLanguages,
|
||||
textLength: sanitizeTextForDetection(metadata.rawText).length,
|
||||
})
|
||||
}, [ax, language])
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
logSuggestion()
|
||||
}, [language])
|
||||
|
||||
return (
|
||||
<LanguageSuggestionButton
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {useCallback, useEffect, useRef} from 'react'
|
||||
import {useCallback, useEffect, useEffectEvent, useRef} from 'react'
|
||||
import {StyleSheet, View} from 'react-native'
|
||||
import {Trans, useLingui} from '@lingui/react/macro'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
@@ -23,6 +23,11 @@ export function CustomFeedEmptyState() {
|
||||
const {currentAccount} = useSession()
|
||||
const hasLoggedDiscoverEmptyErrorRef = useRef(false)
|
||||
|
||||
const logDiscoverEmptyError = useEffectEvent((did: string) => {
|
||||
hasLoggedDiscoverEmptyErrorRef.current = true
|
||||
ax.metric('feed:discover:emptyError', {userDid: did})
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
// Log the empty feed error event
|
||||
if (feedFeedback.feedSourceInfo && currentAccount?.did) {
|
||||
@@ -31,10 +36,7 @@ export function CustomFeedEmptyState() {
|
||||
uri === DISCOVER_FEED_URI &&
|
||||
!hasLoggedDiscoverEmptyErrorRef.current
|
||||
) {
|
||||
hasLoggedDiscoverEmptyErrorRef.current = true
|
||||
ax.metric('feed:discover:emptyError', {
|
||||
userDid: currentAccount.did,
|
||||
})
|
||||
logDiscoverEmptyError(currentAccount.did)
|
||||
}
|
||||
}
|
||||
}, [feedFeedback.feedSourceInfo, currentAccount?.did])
|
||||
|
||||
Reference in New Issue
Block a user