12 Most-Asked React Questions on Stack Overflow Answered for 2026

Stack Overflow's React tag has accumulated over 400,000 questions since 2013. The same underlying problems — state update timing, dependency array mistakes, stale closures inside hooks, re-render behaviour — surface repeatedly under different wordings. Understanding the root causes behind these questions is what separates developers who can navigate novel bugs from those who only recognise problems they have seen before.

These 12 questions are drawn from Stack Overflow's most-viewed React questions as of early 2026, with answers updated to reflect React 18's concurrent features and the hooks-first mental model that most codebases now use.

Q1: Why Is My State Update Not Reflected Immediately?

State updates in React are asynchronous and batched. Calling setState does not mutate state immediately — it schedules a re-render. Reading the state variable on the very next line after calling the setter still returns the old value.

function Counter() {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    setCount(count + 1);
    console.log(count); // Still 0 — the re-render hasn't happened yet
  };
}

In React 18, batching was extended to cover all state updates — including those inside setTimeout, Promise.then, and native event handlers — not just React event handlers as in React 17. This means if you call three setters in a row inside a setTimeout, React batches them into a single re-render rather than triggering three separate renders.

If you need the updated value immediately inside an event handler, compute it as a local variable before passing it to the setter:

const handleClick = () => {
  const newCount = count + 1;
  setCount(newCount);
  doSomethingWith(newCount); // Uses the updated value correctly
};

Q2: How Do I Prevent Unnecessary Re-renders?

React.memo, useMemo, and useCallback are the tools for preventing re-renders, but they are frequently applied in cases where they add overhead rather than save it. Each has a specific use case.

React.memo wraps a component and prevents re-render if props have not changed by shallow comparison. Apply it only to components that render frequently with stable props — the memoization itself has a cost (the comparison), which only pays off if the render cost exceeds it.

useMemo memoizes an expensive computed value. The key word is expensive — running Array.filter or Array.sort on a 10-item list is not expensive. Use it for genuinely heavy computations (large dataset transforms, complex geometry calculations) or when the computed value is used as a dependency in another hook.

useCallback memoizes a function reference. Its primary use case is preventing child component re-renders when passing callbacks as props to components wrapped with React.memo. Without useCallback, a new function reference is created on every parent render, invalidating React.memo's shallow prop comparison.

// Only worth adding useCallback if ChildComponent is wrapped in React.memo
const handleChange = useCallback((value: string) => {
  setFilter(value);
}, []); // Stable function reference across renders

<MemoizedChildComponent onChange={handleChange} />

Q3: Why Does useEffect Run Twice in Development?

React 18's StrictMode intentionally mounts components twice in development — mount, unmount, then mount again. This is designed to surface bugs in effects that do not properly clean up: subscriptions, event listeners, timers, and open connections. If your effect runs twice and breaks something, the cleanup function is missing or incorrect.

The double invocation only happens in development. In production builds, every effect runs exactly once after mount. The correct response to this behaviour is not to remove StrictMode — it is to write proper cleanup functions:

useEffect(() => {
  const controller = new AbortController();

  fetch("/api/data", { signal: controller.signal })
    .then((res) => res.json())
    .then(setData)
    .catch((err) => {
      if (err.name !== "AbortError") console.error(err);
    });

  return () => controller.abort(); // Cleanup cancels the in-flight request
}, []);

Q4: How Do I Fetch Data in React?

The raw useEffect + fetch pattern has three problems that most beginners do not handle: race conditions (a slower earlier request resolves after a faster later one), no deduplication (multiple components mounting simultaneously all fire duplicate requests), and no caching (navigating away and back re-fetches instead of reusing previous data).

In 2026, the recommended approach depends on your rendering context:

  • Next.js App Router (Server Components): fetch directly in the async component. No hook needed.
  • Client-side SPA (Vite/CRA-era apps): TanStack Query (useQuery) or SWR (useSWR). Both handle caching, deduplication, background refetching, and loading/error states.
  • Simple case with no caching needed: useEffect with an AbortController for cleanup (as shown in Q3 above).

Q5: What Is the Difference Between useCallback and useMemo?

useMemo computes and caches a value. useCallback caches a function reference. They are mechanically equivalent — useCallback(fn, deps) is sugar for useMemo(() => fn, deps).

// These two are identical:
const memoFn = useCallback(() => doThing(x), [x]);
const memoFn = useMemo(() => () => doThing(x), [x]);

Use useMemo when you need the result of calling a function (a derived value). Use useCallback when you need the function itself to be stable across renders (to pass as a prop without triggering memo invalidation). The dependency arrays work the same way in both.

Q6: How Do I Share State Between Unrelated Components?

Three approaches exist, ordered by complexity:

  1. Lift state up: Move state to the nearest common ancestor and pass it down via props. Works well for closely related components and avoids external dependencies.
  2. React Context: For state that genuinely needs to be accessible across the tree without prop drilling. Context is not a performance optimisation tool — every consumer re-renders when the context value changes, so use it for low-frequency state (theme, user session, locale).
  3. Zustand (or Jotai/Valtio): For complex shared state that changes frequently. Zustand uses a subscription model where components only re-render when the specific slice of state they subscribe to changes. Zustand stores are defined outside React and work without a Provider — the simplest global state manager that scales well.
// Zustand store — defined outside any component
import { create } from "zustand";

const useCartStore = create<{
  items: string[];
  addItem: (item: string) => void;
}>((set) => ({
  items: [],
  addItem: (item) => set((state) => ({ items: [...state.items, item] })),
}));

// Any component, anywhere in the tree
function CartButton() {
  const addItem = useCartStore((state) => state.addItem);
  return <button onClick={() => addItem("widget")}>Add to Cart</button>;
}

Q7: Why Is My useEffect Causing an Infinite Loop?

Infinite loops in useEffect almost always occur because an object or array is listed as a dependency, and that object is recreated on every render even though its contents are the same. React's dependency comparison uses Object.is, which treats two distinct object references as different even if they have identical properties.

// BUG: options is a new object every render
function Component({ id }: { id: string }) {
  const options = { method: "GET", headers: {} }; // New reference each render

  useEffect(() => {
    fetchData(id, options);
  }, [id, options]); // options triggers re-run → effect creates new render → repeat
}

The fix is to either move the object definition outside the component (if it is truly static), wrap it in useMemo, or move it inside the effect itself so it is not a dependency:

// FIXED: options is inside the effect, not a dependency
useEffect(() => {
  const options = { method: "GET", headers: {} };
  fetchData(id, options);
}, [id]); // Only id is a dependency

Q8: How Do I Pass Data from a Child Component to Its Parent?

React's data flow is unidirectional — parent to child via props. To communicate upward, the parent passes a callback function as a prop and the child calls it with the data.

// Parent
function Parent() {
  const [childValue, setChildValue] = useState("");

  return <Child onValueChange={setChildValue} />;
}

// Child
function Child({ onValueChange }: { onValueChange: (v: string) => void }) {
  return (
    <input
      onChange={(e) => onValueChange(e.target.value)}
      placeholder="Type something"
    />
  );
}

If the parent-child relationship is distant (prop drilling through multiple levels), this pattern becomes cumbersome. That is the signal to reach for Context or a state manager rather than passing callbacks through intermediate components that do not use them.

Q9: What Causes "Cannot Update a Component While Rendering a Different Component"?

This warning appears when you call a state setter during the render phase of another component — essentially triggering a side effect inside the function body rather than inside an effect. A common example is conditionally calling a parent's state setter from inside a child's render:

// BUG: Calling parent setter during render
function Child({ items, onEmpty }: { items: string[]; onEmpty: () => void }) {
  if (items.length === 0) {
    onEmpty(); // This is called during render — triggers the warning
  }
  return <ul>{items.map((i) => <li key={i}>{i}</li>)}</ul>;
}

// FIX: Move the side effect into useEffect
function Child({ items, onEmpty }: { items: string[]; onEmpty: () => void }) {
  useEffect(() => {
    if (items.length === 0) onEmpty();
  }, [items, onEmpty]);

  return <ul>{items.map((i) => <li key={i}>{i}</li>)}</ul>;
}

Q10: How Do I Handle Forms in React?

Two approaches: controlled inputs (React state drives the input value) and uncontrolled inputs (the DOM manages the value and you read it on submit). Controlled inputs give React full knowledge of the form state but require a state update handler for every field. Uncontrolled inputs are simpler but limit React's ability to validate or transform values as the user types.

For any form with more than 3–4 fields, or with validation requirements, React Hook Form is the practical choice. It uses uncontrolled inputs internally via ref, so re-renders are minimal (only the fields that change, not the whole form). Validation is declarative and supports Zod schemas via the @hookform/resolvers package.

import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";

const schema = z.object({
  email: z.string().email("Enter a valid email"),
  phone: z.string().regex(/^[6-9]\d{9}$/, "Enter a valid Indian mobile number"),
});

type FormData = z.infer<typeof schema>;

function ContactForm() {
  const { register, handleSubmit, formState: { errors } } = useForm<FormData>({
    resolver: zodResolver(schema),
  });

  const onSubmit = (data: FormData) => console.log(data);

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register("email")} />
      {errors.email && <p>{errors.email.message}</p>}
      <input {...register("phone")} />
      {errors.phone && <p>{errors.phone.message}</p>}
      <button type="submit">Submit</button>
    </form>
  );
}

Q11: When Should I Use useReducer Instead of useState?

useReducer is suited for state where multiple values change together in response to a single action, or where the next state depends on the current state in non-trivial ways. The reducer pattern — a pure function that takes current state and an action and returns new state — makes complex state transitions predictable and testable in isolation.

Signals that useState is the wrong tool: you have three or more related state variables that always change together, you find yourself reading current state inside state update calls to compute the next value, or your state update logic is spread across multiple event handlers that are hard to reason about independently. Consolidating that into a reducer clarifies the valid transitions and makes the component logic easier to follow.

type State = { status: "idle" | "loading" | "error"; data: string | null; error: string | null };
type Action = { type: "FETCH" } | { type: "SUCCESS"; data: string } | { type: "FAIL"; error: string };

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case "FETCH":   return { status: "loading", data: null, error: null };
    case "SUCCESS": return { status: "idle", data: action.data, error: null };
    case "FAIL":    return { status: "error", data: null, error: action.error };
    default:        return state;
  }
}

function DataComponent() {
  const [state, dispatch] = useReducer(reducer, { status: "idle", data: null, error: null });
  // ...
}

Q12: How Do I Conditionally Render Components?

Several patterns work; the right one depends on whether the condition involves a truthy value or a non-null check, and whether the falsy case should render nothing or an alternative component.

// Short-circuit — renders nothing when isLoaded is false
{isLoaded && <Dashboard />}

// Ternary — renders alternative when isLoaded is false
{isLoaded ? <Dashboard /> : <Skeleton />}

// Nullish check — avoids the pitfall of rendering "0" when count is 0
{count !== 0 && <Badge count={count} />}
// The short-circuit {count && ...} would render the number 0 when count is 0 — a common bug

// For complex conditions, extract to a variable
const content = isAuthenticated
  ? isAdmin ? <AdminPanel /> : <UserDashboard />
  : <LoginPage />;

return <main>{content}</main>;

The "renders 0" bug with {count && <Component />} is one of the most frequently-upvoted bugs on Stack Overflow's React tag. Always use an explicit boolean expression — {count > 0 && ...} or {!!count && ...} — when the left side of && might be a number.

Frequently Asked Questions

Which React questions come up most in Indian developer interviews?

Based on patterns from product company rounds at Swiggy, CRED, Razorpay, and Zepto, the highest-frequency topics are: the difference between useCallback and useMemo (and when not to use either), how React's reconciliation and diffing works, race conditions in data fetching, React 18's automatic batching behaviour, and state management architecture. The useEffect lifecycle — dependency arrays and cleanup — appears in nearly every frontend-specific round. Understanding StrictMode's double-invocation is a common differentiator between candidates who have read the docs and those who have only used tutorials.

Are these Stack Overflow answers still valid for React 19?

Most are valid with a few notes. React 19 introduced the use hook for reading context and promises, changing some async data patterns. The React Compiler (formerly React Forget) in React 19 means manually adding useMemo and useCallback is less necessary when the compiler is enabled — it optimises re-renders automatically. The new form actions API also changes the recommended approach for form handling. The StrictMode double-invocation and batching behaviour remain consistent with React 18.

Where can I practice React concepts for interview preparation?

InterviewBit has a dedicated React section with timed challenges at difficulty levels matching Flipkart and PhonePe rounds. LeetCode's frontend section includes component design and state management problems. GreatFrontEnd (by former Meta and Google engineers) has the most realistic interview-style React problems, including tasks where you rebuild familiar UI components from scratch. For hands-on practice that gives you something to discuss in interviews, build a full-stack mini project — a task manager or Hacker News clone — using React with TanStack Query and deploy it to Vercel.