react-best-practices — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited react-best-practices (Agent Skill) and scored it 91/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 1 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 1 flagged
The text {match} is the classic direct prompt-injection phrasing. Placed in a skill body that the agent reads as trusted instructions, it tries to make the agent abandon its prior rules and follow whatever comes next — a full system-prompt override.
ignore/disregard/forget … previous instructions sentence.Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
UserCard not RenderUser)// ✅ Preferred: explicit props interface
interface UserCardProps {
user: User
onSelect?: (id: string) => void
className?: string
}
export function UserCard({ user, onSelect, className }: UserCardProps) {
return (
<div className={cn('card', className)} onClick={() => onSelect?.(user.id)}>
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
)
}// Lazy initialization for expensive default values
const [count, setCount] = useState(() => computeExpensiveDefault())
// Functional update when new state depends on old state
setCount(prev => prev + 1) // ✅ safe with concurrent rendering
setCount(count + 1) // ❌ stale closure risk// Fetch data on mount + when id changes
useEffect(() => {
let cancelled = false
async function loadUser() {
const user = await fetchUser(id)
if (!cancelled) setUser(user) // prevent state update after unmount
}
loadUser()
return () => { cancelled = true }
}, [id])
// Subscribe / unsubscribe
useEffect(() => {
const subscription = eventBus.on('update', handleUpdate)
return () => subscription.unsubscribe() // always cleanup subscriptions
}, [])useEffect exhaustive deps rule: include every reactive value used inside the effect in the dependency array. Use useCallback/useMemo to stabilize references.
// ✅ DO memoize: callback passed to a child wrapped in React.memo
const handleSubmit = useCallback(async (data: FormData) => {
await api.submit(data)
onSuccess?.()
}, [onSuccess])
// ✅ DO memoize: expensive computation used in render
const sortedItems = useMemo(
() => [...items].sort(compareByDate),
[items]
)
// ❌ DON'T memoize cheap operations - the overhead exceeds savings
const label = useMemo(() => `Hello, ${name}`, [name]) // overkillStart with useState. Lift state up only when siblings need it. Move to global state only when the data is genuinely shared across distant components.
type Action =
| { type: 'increment' }
| { type: 'decrement' }
| { type: 'reset'; payload: number }
function counterReducer(state: number, action: Action): number {
switch (action.type) {
case 'increment': return state + 1
case 'decrement': return state - 1
case 'reset': return action.payload
default: return state
}
}
function Counter() {
const [count, dispatch] = useReducer(counterReducer, 0)
return (
<>
<span>{count}</span>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
</>
)
}// Good for: auth state, theme, locale - NOT high-frequency updates
interface ThemeContextValue {
theme: 'light' | 'dark'
toggleTheme: () => void
}
const ThemeContext = createContext<ThemeContextValue | null>(null)
export function useTheme() {
const ctx = useContext(ThemeContext)
if (!ctx) throw new Error('useTheme must be used within ThemeProvider')
return ctx
}
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState<'light' | 'dark'>('light')
const toggleTheme = useCallback(() => setTheme(t => t === 'light' ? 'dark' : 'light'), [])
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
)
}const UserCard = React.memo(function UserCard({ user, onSelect }: UserCardProps) {
return <div onClick={() => onSelect(user.id)}>{user.name}</div>
})
// Re-renders only when user or onSelect references changenpm install @tanstack/react-virtualimport { useVirtualizer } from '@tanstack/react-virtual'
function VirtualList({ items }: { items: Item[] }) {
const parentRef = useRef<HTMLDivElement>(null)
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
})
return (
<div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
<div style={{ height: virtualizer.getTotalSize() }}>
{virtualizer.getVirtualItems().map(vItem => (
<div
key={vItem.key}
style={{ position: 'absolute', top: vItem.start, width: '100%' }}
>
{items[vItem.index].name}
</div>
))}
</div>
</div>
)
}const HeavyComponent = lazy(() => import('./HeavyComponent'))
function App() {
return (
<Suspense fallback={<Spinner />}>
<HeavyComponent />
</Suspense>
)
}Extract stateful logic into custom hooks for reuse and testability:
// hooks/useLocalStorage.ts
function useLocalStorage<T>(key: string, defaultValue: T) {
const [value, setValue] = useState<T>(() => {
try {
const item = window.localStorage.getItem(key)
return item ? JSON.parse(item) : defaultValue
} catch {
return defaultValue
}
})
const setStoredValue = useCallback((newValue: T | ((prev: T) => T)) => {
setValue(prev => {
const resolved = typeof newValue === 'function'
? (newValue as (prev: T) => T)(prev)
: newValue
try {
window.localStorage.setItem(key, JSON.stringify(resolved))
} catch {}
return resolved
})
}, [key])
return [value, setStoredValue] as const
}'use client' // Next.js App Router: error.tsx must be a client component
class ErrorBoundary extends React.Component<
{ children: React.ReactNode; fallback: React.ReactNode },
{ hasError: boolean }
> {
constructor(props) {
super(props)
this.state = { hasError: false }
}
static getDerivedStateFromError() {
return { hasError: true }
}
componentDidCatch(error: Error, info: React.ErrorInfo) {
console.error('Caught error:', error, info)
}
render() {
return this.state.hasError ? this.props.fallback : this.props.children
}
}
// Usage
<ErrorBoundary fallback={<div>Something went wrong. <button onClick={() => location.reload()}>Retry</button></div>}>
<RiskyComponent />
</ErrorBoundary>// ✅ Buttons with icon-only content need aria-label
<button aria-label="Close dialog" onClick={onClose}>
<XIcon aria-hidden="true" />
</button>
// ✅ Form inputs need associated labels
<label htmlFor="email">Email</label>
<input id="email" type="email" name="email" />
// ✅ Live regions for dynamic content
<div aria-live="polite" aria-atomic="true">
{statusMessage}
</div>
// ✅ Focus management after modal opens
useEffect(() => {
if (isOpen) firstFocusableRef.current?.focus()
}, [isOpen])state.items.push(x)~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.