with one click
react-effect-lints
Use when fixing React hook lint failures in the Keybase client: react-hooks/set-state-in-effect, derived-state effects, prop-change resets, event-hidden-in-effect, stale async results.
Menu
Use when fixing React hook lint failures in the Keybase client: react-hooks/set-state-in-effect, derived-state effects, prop-change resets, event-hidden-in-effect, stale async results.
Migrate React Navigation navigators from dynamic component based config to static object based config.
Software Mansion's best practices for production React Native and Expo apps on the New Architecture. Trigger on: 'React Native', 'Expo', 'New Architecture', 'Reanimated', 'Gesture Handler', 'react-native-svg', 'ExecuTorch', 'react-native-audio-api', 'react-native-enriched', 'Worklet', 'Fabric', 'TurboModule', 'WebGPU', 'react-native-wgpu', 'TypeGPU', 'GPU shader', 'WGSL', 'svg', 'animation', 'gesture', 'audio', 'rich text', 'AI model', 'multithreading', 'chart', 'vector', 'image filter', 'shared value', 'useSharedValue', 'runOnJS', 'scheduleOnRN', 'thread', 'worklet', or any question involving UI, graphics, native modules, or React Native threading and animation behavior. Also use when a more specific sub-skill matches.
Upgrade React Navigation from 6.x to 7.x or from 7.x to 8.x.
Use when asked to look at or address feedback on a PR. Fetches Copilot-only inline comments, skips hidden ones, then evaluates and fixes the valid ones.
Use when upgrading the Keybase client version number - lists all files that must be updated together
This skill should be used when the user asks to "take a desktop screenshot", "screenshot the electron app", "show me the desktop app", "what does the app look like", or mentions checking the Electron/desktop UI visually.
| name | react-effect-lints |
| description | Use when fixing React hook lint failures in the Keybase client: react-hooks/set-state-in-effect, derived-state effects, prop-change resets, event-hidden-in-effect, stale async results. |
Fix the cause of React effect lint errors, not the diagnostic. Target: less state, fewer effects, same behavior.
Effects are usually wrong for transforming props/state for render, resetting state because props changed, or running logic caused by a user event. A fix that wraps setState in setTimeout, Promise.resolve, queueMicrotask, or deferEffectUpdate is almost always a workaround, not a fix.
References:
https://react.dev/learn/you-might-not-need-an-effecthttps://react.dev/reference/eslint-plugin-react-hooks/lints/set-state-in-effectyarn, npm, lint, or TypeScript unless node_modules exists and the user's machine guidance allows it.Delete state and effects that only mirror values from props, store selectors, or other state.
// Avoid
const [visibleItems, setVisibleItems] = React.useState<Array<Item>>([])
React.useEffect(() => {
setVisibleItems(items.filter(item => item.enabled))
}, [items])
// Prefer
const visibleItems = items.filter(item => item.enabled)
This repo uses React Compiler — do not add useMemo by default. Use it only when a calculation is demonstrably expensive or memo identity is required.
If a prop only seeds local state and later prop changes should not reset user edits, use a lazy initializer. Delete effects that keep rewriting local state from the prop.
const [draft, setDraft] = React.useState(() => initialDraft)
If changing the prop should reset the form or modal, use the keyed reset pattern instead.
When a route, username, conversation ID, team ID, or other identity means "this is a different instance", key the inner component.
const Outer = (props: Props) => <Inner key={props.conversationIDKey} {...props} />
const Inner = (props: Props) => {
const [draft, setDraft] = React.useState('')
// ...
}
Keep exported component names stable unless callers need a new export.
First try to store a stable ID and derive the selected object during render — this often removes the need to reset selection at all.
If a prop sometimes controls a value and otherwise the component owns it, derive the visible value during render:
const [internalTab, setInternalTab] = React.useState<Tab>('members')
const selectedTab = props.tab ?? internalTab
const [selectedID, setSelectedID] = React.useState<string | undefined>()
const selected = items.find(item => item.id === selectedID)
If partial adjustment is unavoidable, React allows guarded state updates during render for the same component. Use this sparingly, always with a previous-value guard, and never for side effects.
const [prevItems, setPrevItems] = React.useState(items)
const [selectedID, setSelectedID] = React.useState<string | undefined>()
if (items !== prevItems) {
setPrevItems(items)
setSelectedID(undefined)
}
Do not update another component's state during render. Move timers, navigation, RPCs, logging, and DOM/native work to events or effects.
Put logic in the event handler if it happens because a user clicked, submitted, selected, dismissed, or navigated. Do not infer the event later from a state flag in an effect.
Good candidates: submit RPCs, notifications, navigation from a tab or menu item, clearing waiting state before a mutation.
Effects can still observe external completion of the event (e.g. waiting changing from true to false). Track previous waiting state with a ref when needed.
Keep effects for timers, subscriptions, imperative DOM/native APIs, RPCs keyed by reactive inputs, and updating external stores.
For async work, tag loaded data with the input key and derive the visible value during render to avoid stale data flashes.
type Loaded = {key: string; value: Result}
const [loaded, setLoaded] = React.useState<Loaded | undefined>()
const visible = loaded?.key === requestKey ? loaded.value : undefined
React.useEffect(() => {
let canceled = false
load(requestKey).then(value => {
if (!canceled) setLoaded({key: requestKey, value})
})
return () => { canceled = true }
}, [requestKey])
Prefer request/version IDs over isMounted refs for stale result rejection. If a real mount guard is needed, set it true inside the effect and false in cleanup so Strict Mode remounts don't leave it stuck false.
For timer UI: derive open/closed visibility from current props rather than mirroring into state. Keep cached text only when intentionally delaying removal for an animation.
Kb.* components in .tsx files under shared/; guard any raw DOM with platform constraints.useXState.setState or getState() writes; route through dispatch actions.C.useShallow(...).React.useEffectEvent for stable callbacks called from effects or subscriptions; use React.useLayoutEffect for ref assignment when event handlers need the latest callback after commit. Keep useEffectEvent functions out of dependency arrays.While touching these files:
@/constants/types as a value import (not import type) when T.* is used at runtime.