There's a moment in every React developer's journey where tutorials stop being enough. You can build a to-do app, follow along with a course, maybe even scaffold a Next.js project. But when you look at production codebases from engineers at companies like Stripe, Linear, or Vercel, something feels fundamentally different.
After hundreds of pair programming sessions with senior engineers at YC-backed startups, we've identified the patterns that consistently separate production-ready engineers from “tutorial developers.” Here are the five that come up most often.
01State Architecture Before Components
Junior developers tend to think about components first: “I need a sidebar, a header, a card.” Senior engineers think about data flow first: “Where does this state live? Who needs access to it? What triggers changes?”
This isn't just a preference — it's a completely different starting point. When you design your state architecture before touching JSX, you end up with components that are simpler, more composable, and dramatically easier to debug.
// State scattered across components
function Dashboard() {
const [user, setUser] = useState(null);
const [posts, setPosts] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
const [selectedPost, setSelectedPost] = useState(null);
const [isEditing, setIsEditing] = useState(false);
// ... 10 more useState calls
}// State colocated with a clear mental model
function Dashboard() {
const { user } = useAuth();
const { data: posts, isLoading, error } = usePosts(user?.id);
const [editing, setEditing] = useState<Post | null>(null);
// State is either:
// 1. Server state (managed by data-fetching layer)
// 2. UI state (minimal, local, obvious)
}The mental shift: Before creating any component, ask yourself: “Is this server state or UI state?” Server state belongs in your data layer (React Query, SWR, etc.). UI state should be as local and minimal as possible.
02Custom Hooks as Business Logic Documentation
Most tutorials teach custom hooks as a “reusability” mechanism. Senior engineers use them for something more valuable: making business logic readable and testable, even when the hook is only used once.
// This hook name tells you exactly what the app does
function useAutoSaveDraft(postId: string) {
const [draft, setDraft] = useState('');
const debouncedDraft = useDebounce(draft, 1000);
useEffect(() => {
if (debouncedDraft) {
saveDraft(postId, debouncedDraft);
}
}, [debouncedDraft, postId]);
return { draft, setDraft };
}
// The component reads like a story
function PostEditor({ postId }: { postId: string }) {
const { draft, setDraft } = useAutoSaveDraft(postId);
const { canPublish } = usePublishPermissions(postId);
const { submit, isSubmitting } = usePublishPost(postId);
// ...
}Notice how the component reads like a story: “This editor auto-saves drafts, checks publish permissions, and can submit a post.” That's not accidental — it's what happens when you extract business logic into hooks named after what they do, not how they work.
03Knowing When NOT to Use useEffect
This might be the single biggest signal. Junior codebases are full of useEffect chains that synchronize state with other state. Senior engineers know that most of those effects should be either derived values or event handlers.
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [fullName, setFullName] = useState('');
// This entire effect is unnecessary
useEffect(() => {
setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
// Just compute it. No effect needed.
const fullName = firstName + ' ' + lastName;The React docs now explicitly call this out as a common mistake. The rule of thumb: if you can calculate something from existing state or props during render, don't put it in state. And if you're using useEffect to “react to” a user action, it should probably be an event handler instead.
Want to learn these patterns hands-on?
Book a 1-on-1 pair programming session with a senior React engineer from a YC startup. Work on your code, get real-time feedback.
Book a Session — from $6904Error Boundaries and Loading States as First-Class Concerns
Tutorial code always assumes the happy path. Production code assumes everything will break. Senior engineers think about error states and loading states before they build the success state.
function UserProfile({ userId }: { userId: string }) {
const { data: user, isLoading, error } = useUser(userId);
if (error) {
return <ErrorCard message="Couldn't load profile" retry />;
}
if (isLoading) {
return <ProfileSkeleton />;
}
// Happy path only reached when we KNOW we have data
return <Profile user={user} />;
}Even better, senior engineers use React's Suspense and Error Boundary APIs to handle these concerns declaratively, keeping the component focused purely on the happy path while letting the boundaries handle the rest.
05Performance Intuition (Not Premature Optimization)
Senior engineers don't memo everything. They don't slapuseMemoon every calculation. Instead, they have an intuition for what will cause problems and address it before it becomes a performance bug.
// A large filtered list? This WILL cause jank if unoptimized.
const filteredItems = useMemo(
() => items.filter(item => matchesSearch(item, query)),
[items, query]
);
// But a simple string concat? Don't bother memoizing.
const greeting = `Hello, ${user.name}`; // just compute it
// And if you're passing callbacks to a long list:
const handleSelect = useCallback(
(id: string) => setSelected(id),
[]
);The key insight isn't knowing how to optimize — it's knowing when. A large list being re-filtered on every keystroke? That needs attention. A simple derived value in a component that renders twice a second? Leave it alone.
The Fastest Way to Learn These Patterns
Reading about patterns is a start, but the real learning happens when you apply them to your own code with someone watching. That's why pair programming with a senior engineer is so effective — they can see your codebase, catch your patterns (good and bad), and show you exactly where these principles apply.
At DevPair, every session is a live pair programming session with a senior React engineer from a YC-backed startup. You share your screen, work on your code, and walk away with concrete improvements. It's the difference between reading about swimming and having a coach in the pool with you.