Curated questions covering hooks, state, props, context, virtual DOM, React Router, performance optimization, Redux, and modern React patterns.
React is a JavaScript library by Meta for building user interfaces. Key features: component-based architecture, virtual DOM for efficient updates, unidirectional data flow, JSX syntax, hooks for state and side effects, and a rich ecosystem (React Router, Redux, React Query).
The Virtual DOM is a lightweight JavaScript representation of the real DOM. When state changes, React creates a new Virtual DOM tree, diffs it against the previous one (reconciliation), and applies only the minimal set of changes to the real DOM. This batching makes updates efficient.
Hooks are functions that let functional components use state and lifecycle features. Introduced in React 16.8 to replace class components. Benefits: reuse stateful logic without HOCs/render props, simpler code, no this keyword confusion.
useState returns a state value and a setter function. The setter triggers a re-render with the new value. State updates are asynchronous and batched.
const [count, setCount] = useState(0);\n\n// Direct value\nsetCount(5);\n\n// Functional update (use when new state depends on old)\nsetCount(prev => prev + 1);
useEffect runs side effects after render. The dependency array controls when it re-runs.
useEffect(() => {\n const sub = subscribe(userId);\n return () => sub.unsubscribe(); // cleanup\n}, [userId]);
useContext reads a value from a React Context without prop drilling. Best for global data like theme, locale, or auth state. Not a replacement for state management in large apps — context re-renders all consumers on every change.
const ThemeContext = createContext("light");\n\nfunction App() {\n return (\n <ThemeContext.Provider value="dark">\n <Child />\n </ThemeContext.Provider>\n );\n}\n\nfunction Child() {\n const theme = useContext(ThemeContext); // "dark"\n}
useRef returns a mutable ref object whose .current property persists across renders without causing re-renders.
const inputRef = useRef(null);\n\nfunction focusInput() {\n inputRef.current.focus();\n}\n\nreturn <input ref={inputRef} />;
useMemo memoizes the result of an expensive computation, recomputing only when dependencies change. Use it when a calculation is genuinely expensive and runs on every render.
const sortedList = useMemo(() => {\n return [...items].sort((a, b) => a.name.localeCompare(b.name));\n}, [items]); // only re-sorts when items changes
const handleClick = useCallback(() => {\n doSomething(id);\n}, [id]); // stable reference unless id changes
React.memo is a HOC that memoizes a functional component, preventing re-renders if props have not changed (shallow comparison). Use it for pure components that render often with the same props.
const UserCard = React.memo(({ user }) => {\n return <div>{user.name}</div>;\n});\n\n// Custom comparison\nconst UserCard = React.memo(({ user }) => (\n <div>{user.name}</div>\n), (prevProps, nextProps) => prevProps.user.id === nextProps.user.id);
// Controlled\nconst [value, setValue] = useState("");\n<input value={value} onChange={e => setValue(e.target.value)} />\n\n// Uncontrolled\nconst ref = useRef();\n<input ref={ref} defaultValue="initial" />
Reconciliation is how React updates the DOM efficiently. React compares the new Virtual DOM tree with the previous one using a diffing algorithm. Key rules: elements of different types produce different trees; use key prop for lists to help React identify which items changed, were added, or removed.
The key prop helps React identify which list items have changed, been added, or removed. Keys must be unique among siblings. Using index as key causes bugs when the list is reordered. Use stable unique IDs.
// Bad - index as key causes issues on reorder\n{items.map((item, i) => <Item key={i} {...item} />)}\n\n// Good - stable unique ID\n{items.map(item => <Item key={item.id} {...item} />)}
useReducer manages complex state logic with a reducer function (like Redux). Prefer it over useState when: state has multiple sub-values, next state depends on previous state in complex ways, or state transitions follow clear action patterns.
const reducer = (state, action) => {\n switch (action.type) {\n case "increment": return { count: state.count + 1 };\n case "reset": return { count: 0 };\n default: return state;\n }\n};\n\nconst [state, dispatch] = useReducer(reducer, { count: 0 });\ndispatch({ type: "increment" });
Context provides a way to pass data through the component tree without prop drilling. Limitations: every consumer re-renders when context value changes (even if they only use part of it); not optimized for high-frequency updates; for complex state, use Zustand, Redux, or Jotai instead.
React Router enables client-side navigation. v6 uses Routes and Route components with element prop.
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";\n\nfunction App() {\n return (\n <BrowserRouter>\n <Routes>\n <Route path="/" element={<Home />} />\n <Route path="/users/:id" element={<User />} />\n <Route path="*" element={<NotFound />} />\n </Routes>\n </BrowserRouter>\n );\n}
Code splitting splits the bundle into smaller chunks loaded on demand. React.lazy() and Suspense enable component-level lazy loading.
const Dashboard = React.lazy(() => import("./Dashboard"));\n\nfunction App() {\n return (\n <Suspense fallback={<Spinner />}>\n <Dashboard />\n </Suspense>\n );\n}
Redux is a predictable state management library. Use it when: multiple components need the same state, state changes are complex, you need time-travel debugging, or the app is large. For simpler cases, Context + useReducer or Zustand are lighter alternatives.
Redux Toolkit (RTK) is the official recommended way to write Redux. It eliminates boilerplate with createSlice (combines actions + reducer), createAsyncThunk (async actions), and configureStore (sets up middleware automatically).
const counterSlice = createSlice({\n name: "counter",\n initialState: { value: 0 },\n reducers: {\n increment: state => { state.value += 1; }, // Immer allows mutation\n decrement: state => { state.value -= 1; }\n }\n});\nexport const { increment, decrement } = counterSlice.actions;
const count = useSelector(state => state.counter.value);\nconst dispatch = useDispatch();\n\nreturn <button onClick={() => dispatch(increment())}>+{count}</button>;
A HOC is a function that takes a component and returns a new enhanced component. Used for cross-cutting concerns like authentication, logging, and data fetching. Largely replaced by hooks in modern React.
function withAuth(Component) {\n return function AuthenticatedComponent(props) {\n const { isLoggedIn } = useAuth();\n if (!isLoggedIn) return <Redirect to="/login" />;\n return <Component {...props} />;\n };\n}
Render props is a pattern where a component accepts a function as a prop and calls it to render UI. Enables sharing stateful logic between components. Largely replaced by hooks.
function MouseTracker({ render }) {\n const [pos, setPos] = useState({ x: 0, y: 0 });\n return (\n <div onMouseMove={e => setPos({ x: e.clientX, y: e.clientY })}>\n {render(pos)}\n </div>\n );\n}\n\n<MouseTracker render={({ x, y }) => <p>{x}, {y}</p>} />
forwardRef passes a ref from a parent to a child DOM element or component. useImperativeHandle customizes what the parent sees when it accesses the ref — exposing only specific methods instead of the full DOM node.
const Input = forwardRef((props, ref) => {\n useImperativeHandle(ref, () => ({\n focus: () => inputRef.current.focus(),\n clear: () => inputRef.current.value = ""\n }));\n const inputRef = useRef();\n return <input ref={inputRef} {...props} />;\n});
Suspense lets components wait for something (lazy-loaded component, data fetch) before rendering. Shows a fallback UI while waiting. Works with React.lazy() and data-fetching libraries like React Query and Relay.
<Suspense fallback={<LoadingSpinner />}>\n <LazyComponent />\n <DataComponent /> {/* with use() hook in React 19 */}\n</Suspense>
// React 18 concurrent mode\nimport { createRoot } from "react-dom/client";\nconst root = createRoot(document.getElementById("root"));\nroot.render(<App />);
useTransition marks a state update as non-urgent, allowing React to keep the UI responsive while the update is processed. Returns isPending (boolean) and startTransition (function).
const [isPending, startTransition] = useTransition();\n\nfunction handleSearch(value) {\n setInputValue(value); // urgent - update input immediately\n startTransition(() => {\n setSearchResults(filterItems(value)); // non-urgent\n });\n}
useDeferredValue defers updating a value until the browser is idle, similar to useTransition but for values you do not control (e.g., from props). Useful for deferring expensive re-renders.
const deferredQuery = useDeferredValue(query);\n\n// ExpensiveList only re-renders when browser is idle\nreturn (\n <>\n <input value={query} onChange={e => setQuery(e.target.value)} />\n <ExpensiveList query={deferredQuery} />\n </>\n);
In React 17, state updates inside setTimeout, Promises, and native event handlers were not batched. React 18 automatically batches all state updates regardless of where they occur, reducing unnecessary re-renders.
// React 18: both setCount and setFlag trigger only ONE re-render\nsetTimeout(() => {\n setCount(c => c + 1);\n setFlag(f => !f);\n}, 1000);
const { data, isLoading, error } = useQuery({\n queryKey: ["users"],\n queryFn: () => fetch("/api/users").then(r => r.json())\n});
React Server Components (RSC) run on the server and never ship their JavaScript to the client. They can directly access databases and file systems. They cannot use hooks or browser APIs. Client Components use "use client" directive. RSC is the foundation of Next.js App Router.
// Server Component (default in Next.js App Router)\nasync function UserList() {\n const users = await db.query("SELECT * FROM users"); // direct DB access\n return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;\n}
useId generates a unique, stable ID that is consistent between server and client renders. Use it for accessibility attributes (htmlFor, aria-describedby) to avoid hydration mismatches.
function FormField({ label }) {\n const id = useId();\n return (\n <>\n <label htmlFor={id}>{label}</label>\n <input id={id} />\n </>\n );\n}
Prop drilling is passing props through multiple intermediate components that do not use them. Solutions: Context API (for global data), component composition (pass components as props), state management libraries (Redux, Zustand), or custom hooks.
Both group elements without adding a DOM node. React.Fragment supports the key prop (required in lists). Empty tags (<>>) are shorthand and do not support key.
// Supports key prop - use in lists\n{items.map(item => (\n <React.Fragment key={item.id}>\n <dt>{item.term}</dt>\n <dd>{item.desc}</dd>\n </React.Fragment>\n))}
defaultProps is the legacy way to set default prop values for class and function components. Modern React uses ES6 default parameter values in function signatures, which is simpler and works with TypeScript better. defaultProps is deprecated for function components.
// Modern approach (preferred)\nfunction Button({ label = "Click me", disabled = false }) {\n return <button disabled={disabled}>{label}</button>;\n}
Zustand is a lightweight state management library. Compared to Redux: no boilerplate (no actions/reducers/selectors), no Provider needed, simpler API, smaller bundle. Best for small-to-medium apps. Redux Toolkit is better for large apps needing strict patterns and devtools.
const useStore = create((set) => ({\n count: 0,\n increment: () => set(state => ({ count: state.count + 1 })),\n reset: () => set({ count: 0 })\n}));\n\nconst { count, increment } = useStore();
useEffect cleanup function runs both on unmount AND before the next effect runs (when dependencies change). componentWillUnmount only runs on unmount. This means useEffect cleanup is more powerful — it prevents stale subscriptions on every dependency change.
useEffect(() => {\n const ws = new WebSocket(url);\n ws.onmessage = handleMessage;\n return () => ws.close(); // runs on unmount AND when url changes\n}, [url]);
use() (React 19) is a new hook that reads the value of a Promise or Context. Unlike other hooks, it can be called conditionally. It integrates with Suspense — the component suspends while the Promise is pending.
// React 19\nfunction UserProfile({ userPromise }) {\n const user = use(userPromise); // suspends until resolved\n return <h1>{user.name}</h1>;\n}
StrictMode is a development tool that helps find bugs by: double-invoking render functions and effects to detect side effects, warning about deprecated APIs, and detecting unexpected side effects. It has no effect in production builds.
// React 19 useOptimistic\nconst [optimisticLikes, addOptimisticLike] = useOptimistic(\n likes,\n (state, newLike) => [...state, newLike]\n);
Portals render a component into a different DOM node than its parent, while keeping it in the React component tree. Used for modals, tooltips, and dropdowns that need to escape overflow:hidden or z-index constraints.
function Modal({ children }) {\n return ReactDOM.createPortal(\n <div className="modal">{children}</div>,\n document.getElementById("modal-root")\n );\n}
flushSync forces React to flush all pending state updates synchronously before returning. Use it when you need the DOM to update immediately (e.g., before reading a DOM measurement). Overuse hurts performance.
import { flushSync } from "react-dom";\n\nflushSync(() => {\n setCount(1); // DOM updated synchronously before next line\n});\nconsole.log(ref.current.textContent); // sees updated DOM
Error boundaries are class components (or react-error-boundary library) that catch JavaScript errors in their child component tree during rendering, lifecycle methods, and constructors. try/catch only works for imperative code and event handlers — it cannot catch errors in render.
import { ErrorBoundary } from "react-error-boundary";\n\n<ErrorBoundary fallback={<ErrorPage />}>\n <App />\n</ErrorBoundary>
PureComponent implements shouldComponentUpdate with a shallow comparison of props and state, preventing re-renders when nothing has changed. Component always re-renders when setState is called. In functional components, React.memo is the equivalent of PureComponent.
Rendering creates the DOM from scratch (CSR). Hydration attaches React event listeners to server-rendered HTML without recreating the DOM. React 18 introduces selective hydration — high-priority parts hydrate first, and Suspense boundaries can defer hydration of less important parts.
Explore 500+ free tutorials across 20+ languages and frameworks.