Mastering the React Interview top Questions and Best Practices
Updated: March 27, 2026
TL;DR
React interviews in 2026 focus on React 19's Server Components (stable since December 2024), Actions and the new form hooks (useActionState, useOptimistic, useFormStatus), the use() API, ref-as-a-prop, and the React Compiler (stable v1.0 since October 2025). Be ready to explain when to render on the server vs. the client, how Actions handle pending and error states automatically, and why automatic memoization changes how you structure components. Familiarity with the Next.js App Router is expected since it's the most common React 19 deployment target.
React has evolved dramatically since its early days. What was a cutting-edge pattern two years ago might be considered legacy today. If you're preparing for a React interview in 2026, you need to understand not just the fundamentals — which haven't changed — but also the modern paradigm: React 19 (stable since December 5, 2024), Server Components, the Actions model, and React Compiler v1.0 (stable since October 7, 2025). React 19.2 (October 2025) added the <Activity /> component, useEffectEvent, and Partial Pre-rendering — interviewers at companies on the latest minor will ask about these too.
This guide covers the questions you're likely to encounter, organized by concept with practical answers and code examples. Whether you're interviewing for your first React role or advancing to a senior position, these topics form the foundation of modern React development.
Understanding React 19's Core Paradigm Shift
Q: What's the fundamental difference between Server Components and Client Components?
A: Server Components became stable in React 19 (December 2024) after years in the Canary channel. They execute only on the server, never ship JavaScript to the client, and can access databases, APIs, and secrets directly. Client Components execute in the browser and are the only place hooks like useState and useEffect are valid.
In a framework like the Next.js App Router, Server Components are the default. You add the 'use client' directive at the top of a file to opt that module (and everything it imports) into the Client Component graph:
// app/posts/PostList.tsx — Server Component (default)
export default async function PostList() {
const posts = await db.posts.findAll();
return (
<ul>
{posts.map(post => <li key={post.id}>{post.title}</li>)}
</ul>
);
}
// app/Counter.tsx — Client Component (explicitly marked)
'use client';
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
Why it matters: Server Components reduce JavaScript shipped to the browser, eliminate one layer of data-fetching waterfall, and let secrets stay on the server. They are not a replacement for Client Components — they're complementary.
Q: How do Server Components affect data fetching?
A: In Server Components, you can use async/await directly without moving to an API route. You fetch data server-side before rendering:
// No API route needed
export default async function UserProfile({ userId }) {
const user = await fetch(`https://api.example.com/users/${userId}`).then(r => r.json());
return (
<div>
<h1>{user.name}</h1>
<p>{user.bio}</p>
</div>
);
}
If the component needs interactivity (like a form), you extract the interactive part into a Client Component and pass the data as props.
React Actions and Form Handling
Q: What are React Actions and why do they matter? (And how are they different from Server Actions?)
A: This is a common interview trap, so be precise. Actions in React 19 are async functions passed to async transitions — React tracks pending state, handles errors via Error Boundaries, and supports optimistic updates. They run wherever they're defined; nothing about an Action requires a server. Server Actions are Actions marked with the 'use server' directive, which makes them callable from Client Components but executed on the server. The Action machinery is the same; the directive is what makes it server-only.
React 19 also gave <form> first-class support: pass a function to the action (or formAction) prop, and React calls it with the form data inside a transition.
'use client';
import { createPost } from './actions';
export default function CreatePostForm() {
return (
<form action={createPost}>
<input type="text" name="title" required />
<textarea name="content" required />
<button type="submit">Create Post</button>
</form>
);
}
And in actions.ts — this is a Server Action:
'use server';
import { db } from '@/lib/db';
import { revalidatePath } from 'next/cache';
export async function createPost(formData: FormData) {
const title = formData.get('title');
const content = formData.get('content');
await db.posts.create({ title, content });
revalidatePath('/posts');
}
Q: How do useActionState, useOptimistic, and useFormStatus fit in?
A: These are the three new hooks that surround Actions. Knowing what each one does is the most common React 19 follow-up question.
useActionState(action, initialState)— wraps an Action and returns[state, dispatch, isPending]. Use it to track the result of the last submission and the in-flight state.useOptimistic(currentState)— lets you render an optimistic update immediately while the Action is still in flight; React reverts automatically if the Action fails.useFormStatus()— called from a child of a<form>to read the parent form's pending status (used to disable a submit button while submitting, for example).
'use client';
import { useActionState, useOptimistic } from 'react';
import { useFormStatus } from 'react-dom';
import { createPost } from './actions';
function SubmitButton() {
const { pending } = useFormStatus();
return <button type="submit" disabled={pending}>{pending ? 'Saving...' : 'Save'}</button>;
}
export default function PostForm({ posts }) {
const [optimisticPosts, addOptimistic] = useOptimistic(
posts,
(state, newPost) => [...state, newPost],
);
const [error, submit] = useActionState(async (_prev, formData) => {
addOptimistic({ title: formData.get('title'), pending: true });
return await createPost(formData);
}, null);
return (
<form action={submit}>
<input name="title" required />
<SubmitButton />
{error && <p role="alert">{error}</p>}
<ul>{optimisticPosts.map(p => <li key={p.title}>{p.title}</li>)}</ul>
</form>
);
}
Note: useFormStatus is imported from react-dom, not react. Catch this and you'll stand out.
The use() API and Async Operations
Q: When do you use the use() API?
A: use() is a new API (not technically a hook — it can be called inside loops and conditionals, unlike the rules-of-hooks-bound useContext). It reads the value from a resource: a Promise or a Context. When called with a Promise, the component suspends until the Promise resolves, and the nearest <Suspense> boundary shows its fallback.
'use client';
import { use } from 'react';
export default function PostContent({ postPromise }) {
const post = use(postPromise); // suspends until resolved
return (
<article>
<h1>{post.title}</h1>
<p>{post.body}</p>
</article>
);
}
A Server Component starts the fetch (without await-ing it) and passes the unresolved Promise down to a Client Component:
import { Suspense } from 'react';
import { PostContent } from './PostContent';
export default function PostPage({ slug }) {
// No await — kick off the fetch, let the Client Component suspend on it.
const postPromise = fetch(`https://api.example.com/posts/${slug}`).then(r => r.json());
return (
<Suspense fallback={<p>Loading...</p>}>
<PostContent postPromise={postPromise} />
</Suspense>
);
}
This pattern lets you stream data across the server-client boundary without blocking the server render.
use() also works for Context — and unlike useContext, it can be called conditionally (after early returns, inside if branches), which is one reason it isn't classified as a hook.
React Compiler and Automatic Memoization
Q: What does React Compiler do, and is it stable?
A: React Compiler v1.0 shipped as stable on October 7, 2025, after roughly 18 months of experimental, beta, and RC phases. It is a Babel plugin that automatically memoizes components, values, and callbacks at build time, so you no longer need to manually reach for useMemo, useCallback, or React.memo for typical optimization.
It is opt-in for existing apps. For new projects, Expo SDK 54+ enables it by default, while Vite (create-vite) and Next.js (create-next-app) let you choose a compiler-enabled template during setup rather than defaulting to it. It's compatible with React 17 and up — you don't strictly need React 19, though pairing it with React 19 is the common case.
// Before: manual optimization
function ExpensiveComponent({ data, onUpdate }) {
const cachedValue = useMemo(() => processData(data), [data]);
const cachedCallback = useCallback(() => onUpdate(cachedValue), [cachedValue, onUpdate]);
return <Child value={cachedValue} onChange={cachedCallback} />;
}
// With React Compiler: no optimization code needed
function ExpensiveComponent({ data, onUpdate }) {
const cachedValue = processData(data); // Compiler auto-memoizes
const cachedCallback = () => onUpdate(cachedValue); // Auto-memoized callback
return <Child value={cachedValue} onChange={cachedCallback} />;
}
Interview follow-ups to expect: "Do you still need useMemo?" (Generally no inside compiled code, but yes for isolating very expensive computations or when the compiler bails out due to dynamic patterns.) "How do you adopt it incrementally?" (Per-directory or per-file via the compiler config — see the official incremental adoption guide.)
ref as a Prop, and forwardRef
Q: How do refs work in React 19?
A: In React 19, function components accept ref as a regular prop. forwardRef is no longer needed for new components and will be deprecated in a future major:
// React 19: ref is just a prop
function MyInput({ placeholder, ref }) {
return <input placeholder={placeholder} ref={ref} />;
}
// Usage stays the same
<MyInput ref={inputRef} />
You should still recognize forwardRef because every codebase older than late 2024 uses it. React 19 also added support for ref cleanup functions — if a ref callback returns a function, React calls it on unmount, mirroring useEffect cleanup semantics.
Next.js App Router Patterns
Q: How do you structure a full-stack feature in Next.js App Router?
A: The pattern is: Server Component → Client Component for interactivity → Action for mutations → Cache revalidation.
// app/posts/page.tsx - Server Component
import { PostList } from './components/PostList';
export default async function PostsPage() {
const posts = await db.posts.findAll();
return (
<div>
<h1>Posts</h1>
<PostList initialPosts={posts} />
</div>
);
}
// components/PostList.tsx - Client Component
'use client';
import { useState } from 'react';
import { deletePost } from '../actions';
export function PostList({ initialPosts }) {
const [posts, setPosts] = useState(initialPosts);
async function handleDelete(id) {
await deletePost(id);
setPosts(posts.filter(p => p.id !== id));
}
return (
<ul>
{posts.map(post => (
<li key={post.id}>
{post.title}
<button onClick={() => handleDelete(post.id)}>Delete</button>
</li>
))}
</ul>
);
}
// actions.ts
'use server';
import { db } from '@/lib/db';
import { revalidatePath } from 'next/cache';
export async function deletePost(id: string) {
await db.posts.delete(id);
revalidatePath('/posts');
}
Legacy Patterns (Still Asked)
Q: What are class components and when are they still relevant?
A: Class components are the pre-hooks way of managing state and side effects. They're rarely used in new code but appear in legacy codebases:
class Counter extends React.Component {
state = { count: 0 };
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Increment
</button>
</div>
);
}
}
Functional components with hooks are the modern standard. If you encounter class components in an interview, show you understand them but emphasize hooks-based approaches.
Q: What's the difference between useEffect and server-side data fetching?
A: useEffect runs in the browser after rendering. If you can fetch data server-side (in a Server Component or Action), you should—it's faster and doesn't create a loading waterfall:
// Slower: client-side fetch with waterfall
'use client';
import { useEffect, useState } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(r => r.json())
.then(setUser);
}, [userId]);
return user ? <p>{user.name}</p> : <p>Loading...</p>;
}
// Faster: server-side fetch
export default async function UserProfile({ userId }) {
const user = await fetch(`https://api.example.com/users/${userId}`).then(r => r.json());
return <p>{user.name}</p>;
}
React 19.2 Additions Worth Knowing
React 19.2 shipped on October 1, 2025. Most teams on the latest minor have adopted at least one of these.
<Activity />— pre-renders or preserves UI state for parts of the app the user is likely to navigate to (or away from), without unmounting. Replaces a lot of manual "keep this mounted but hidden" patterns.useEffectEvent— separates event-style logic from a reactive Effect. The returned function always sees the latest props/state without retriggering the Effect when it changes. Solves the "stale closure insideuseEffect" problem cleanly.- Partial Pre-rendering — pre-render the static shell, stream the dynamic parts later. Particularly relevant if your interviewer mentions Next.js PPR.
- Suspense SSR Batching — server-rendered Suspense boundaries are revealed in batches, smoothing the perceived load.
cacheSignal— for Server Components, anAbortSignalthat fires when a cached resource is no longer needed.
Common Interview Topics
React Hooks Overview (React 19.2):
useState: state managementuseEffect: side effects and cleanupuseEffectEvent(19.2): non-reactive event handlers inside EffectsuseContext: read context (note:use(Context)works conditionally)useReducer: complex state logicuseCallback/useMemo: manual memoization (often unnecessary with React Compiler)useRef: access DOM elements or mutable storageuseTransition: mark state updates as non-urgentuseDeferredValue: defer rendering of an expensive valueuseActionState(19): pending/result state for ActionsuseOptimistic(19): optimistic UI during async transitionsuseFormStatus(19, fromreact-dom): read parent form's pending stateuse()(19): read a Promise or Context, can be called conditionally
Performance Optimization:
- Adopt React Compiler instead of hand-written
useMemo/useCallback - Code splitting with dynamic imports and
React.lazy - Image optimization (AVIF, WebP) and the framework's
<Image>component - Bundle analysis (rollup-plugin-visualizer for Vite,
@next/bundle-analyzerfor Next.js) - Lazy loading with
Suspenseboundaries - React DevTools Profiler for identifying expensive renders
Testing Best Practices:
- Unit tests with Vitest or Jest
- Component testing with React Testing Library
- Integration tests for full features
- E2E tests with Playwright or Cypress
Key Takeaways
React in 2026 is fundamentally about server-first rendering with progressive interactivity. Interviews expect you to articulate the React 19 model — Server Components are stable, Actions are the new mutation primitive, use() reads Promises and Context, refs are plain props, and the React Compiler (v1.0, October 2025) handles memoization for you. React 19.2 (October 2025) added <Activity />, useEffectEvent, and Partial Pre-rendering — knowing these signals you're current.
Be precise about the boundary between Actions (the React 19 transition primitive) and Server Actions (Actions plus 'use server'). Be honest about what Server Components can and can't do — they aren't a wholesale replacement for Client Components, and the value comes from using each where it fits.
Master these core concepts, understand the Next.js App Router, and be prepared to discuss both modern patterns and legacy code. Interviewers want to see that you understand why certain choices exist, not just how to write the code.
Sources
- React v19 release notes (December 5, 2024) — confirms Actions,
useActionState,useOptimistic,useFormStatus,use(), document metadata, ref-as-prop, and Server Components stable in 19 - React 19.2 release notes (October 1, 2025) —
<Activity />,useEffectEvent, Partial Pre-rendering, Suspense SSR Batching,cacheSignal - React Compiler v1.0 announcement (October 7, 2025) — confirms stable status, React 17+ compatibility, and adoption defaults in Expo, Vite, and Next.js templates