After running technical reviews on ~600 React code samples in the last 18 months at Gaper.io, I've started to notice the same five patterns separating engineers who ship clean React from those who don't. Sharing in case it helps you tighten your own hiring filter.
1. They reach for useState before reaching for state libraries
The single biggest red flag in 2026 React code is a take-home that imports Zustand or Redux for what should be 3 useState calls. State libraries are tools for specific problems (multi-tree updates, time-travel debugging, complex middleware). They are not the default.
Strong candidates show a clear progression in their code:
-
useStatefor local component state - Lift state up to common ancestor
-
useReducer+ Context for cross-tree state - Zustand / Jotai / Redux only when you have a specific reason
If a candidate's first instinct on a "build a counter" problem is npm install zustand, you have a complexity tell.
2. They handle useEffect dependencies correctly
The classic anti-pattern still shows up in roughly 40% of code samples we review:
// Recreates the user object every render → infinite loop or stale closure
useEffect(() => {
fetchData(user);
}, [user]);
// Stable primitive dependency
useEffect(() => {
fetchData(user);
}, [user.id]);
The React Compiler will eventually fix this for everyone, but most production codebases aren't on it yet. Engineers who reach for primitive dependencies, or use useMemo deliberately, ship cleaner production code.
3. They know when NOT to memoize
useMemo and useCallback everywhere is performative optimization. We see take-homes where every function in a component is wrapped in useCallback with a 5-item dependency array that's slower than not memoizing at all.
The senior take is: profile first, memoize second. The candidates who can articulate "I'd memoize this only if it's a dependency of an expensive child" are the ones I want on a real codebase.
4. They use the right data-fetching layer for the shape of the data
In 2026, the right pattern depends on the data:
- Server state (API responses, paginated lists, mutations) → React Query / SWR / TanStack Query
- Form state → React Hook Form + Zod
- URL state (filters, search, pagination cursors) →
nuqsor built-in router APIs - Client UI state →
useState/useReducer
Candidates who try to manage server state in useState (or worse, in Redux) signal they haven't worked on a real product yet.
5. They write components that are easy to delete
This one is harder to test but matters most in production. The strongest React engineers write components with a small, clear surface area, no hidden global side effects, and explicit prop interfaces. The kind of component you can rip out in 30 seconds when requirements change.
The tell in code review: do their components import from 5+ random places? Do they reach into a global store from inside a leaf component? Do they have implicit dependencies on parent layout? Each of these makes the component a future migration headache.
How we test for this in practice
We run a 4-stage technical filter for React engineers:
- Async take-home (4 hours, real-product problem with intentional ambiguity we read code AND read your README)
- Live debugging (45 min, you screen-share against a broken React + TS codebase)
- System design (45 min pick a feature, walk us through state, data flow, and component boundaries)
- Reference verification (30 min with someone you actually shipped with)
The first two filters alone catch ~70% of mis-leveling.
If you're hiring React engineers and want to talk through how to operationalize this kind of filter, Gaper.io has a vetted bench of senior React engineers you can pilot with most engagements start with a 2-week paid trial.
What's the one filter you'd add to this list? Curious what other teams use to catch mis-leveled React candidates before the offer goes out.
Top comments (0)