Unit 3: State, Events, Hooks, and Side Effects - Subjective Questions
INT252 — Web App Development With Reactjs • Practice Questions with Detailed Answers
20 questions
Explain the event handling mechanism in React. How does it differ from event handling in traditional HTML?
React handles user interactions through event handlers passed to JSX elements as props.
- React event names use camelCase, such as
onClick,onChange, andonSubmit. - An event handler receives a function rather than a string containing JavaScript code.
- React provides a cross-browser event wrapper known as a SyntheticEvent.
- The event object provides methods such as
preventDefault()andstopPropagation().
Example:
function Button() {
function handleClick(event) {
console.log("Button clicked");
}
return <button onClick={handleClick}>Click</button>;
}
In traditional HTML, an event may be written as onclick="handleClick()", whereas React uses onClick={handleClick}. React's approach keeps event logic within JavaScript functions and integrates it with the component model.
Describe how arguments can be passed to React event handlers. Also explain the purpose of the event object.
Arguments can be passed to an event handler by using an arrow function or bind().
Using an arrow function:
<button onClick={(event) => deleteItem(itemId, event)}>
Delete
</button>
Using bind():
<button onClick={deleteItem.bind(null, itemId)}>
Delete
</button>
The event object contains information about the event, including:
- The element that triggered the event through
event.target. - The event type through
event.type. - Keyboard or mouse-related data.
- Methods such as
event.preventDefault()andevent.stopPropagation().
A function must be passed, not immediately invoked. For example, onClick={handleClick} is correct, while onClick={handleClick()} calls the function during rendering.
Define local state in React and explain how the useState hook is used to manage it.
Local state is data owned and managed by a particular component. When local state changes, React schedules the component to render again with the updated value.
The useState hook returns an array containing:
- The current state value.
- A setter function used to request an update.
Example:
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
Here, 0 is the initial state, count stores the current value, and setCount schedules an update. State should not be modified directly because React relies on the setter to recognize and process the change.
Explain functional state updates in React. Why are they important when the next state depends on the previous state?
A functional state update passes an updater function to the state setter. React supplies the latest pending state to that function.
Example:
setCount(previousCount => previousCount + 1);
Functional updates are important because state values inside an event handler represent the values from the render in which that handler was created. Multiple direct updates may therefore use the same captured value:
setCount(count + 1);
setCount(count + 1);
These statements can result in only one effective increment. Functional updates correctly apply each operation in sequence:
setCount(value => value + 1);
setCount(value => value + 1);
The second version produces an increment of two because each updater receives the result of the preceding updater.
Discuss React's state update behavior and batching. Illustrate your answer with an appropriate example.
React state setters do not immediately change the state variable in the currently executing code. They schedule updates for a future render. React can also group multiple updates into a single render, a behavior known as batching.
Example:
function handleClick() {
setName("Asha");
setAge(21);
setActive(true);
}
React can process these updates together and perform one render instead of rendering separately after every setter call. Batching improves performance and prevents partially updated user interfaces.
Important consequences include:
- State behaves like a snapshot for a particular render.
- Reading a state variable immediately after calling its setter normally returns the old snapshot.
- Updates that depend on previous state should use functional updater functions.
- React may skip a render when the new state is equal to the old state according to
Object.iscomparison.
Developers should write code based on declarative state transitions instead of assuming that setters modify variables synchronously.
What causes a React component to re-render? Explain the major re-render triggers and their relationship with child components.
A React component may re-render due to the following major triggers:
- Its local state is updated.
- Its parent component re-renders, causing React to evaluate the child again by default.
- It receives changed props.
- A context value consumed by the component changes.
- An external store subscription used by the component reports a change.
A re-render means React calls the component function again to produce a new element tree. It does not necessarily mean that every corresponding DOM node is replaced. React compares the new output with the previous output and commits only the necessary DOM changes.
Child components are normally evaluated when their parent renders. React.memo can allow a child to skip rendering when its props are unchanged, but it is only useful when the saved work is greater than the comparison cost. Context changes and the child's own state updates can still cause a memoized child to render.
Explain how objects and arrays should be updated when they are stored in React state. Why is direct mutation considered an anti-pattern?
Objects and arrays stored in state should be treated as immutable. Instead of changing the existing value, a component should create a new object or array and pass it to the setter.
Object update:
setUser(previousUser => ({
...previousUser,
name: "Meera"
}));
Array addition:
setItems(previousItems => [
...previousItems,
newItem
]);
Array update:
setItems(previousItems =>
previousItems.map(item =>
item.id === targetId
? { ...item, completed: true }
: item
)
);
Direct mutation, such as user.name = "Meera", is an anti-pattern because:
- The object reference may remain unchanged.
- React may not recognize the update correctly.
- Previous render snapshots can be corrupted.
- Memoization and equality checks become unreliable.
- State behavior becomes more difficult to debug and predict.
Define derived state. Distinguish between computing a derived value during rendering and storing that value as separate state.
Derived state is a value that can be calculated from existing props or state. In most cases, it should be computed during rendering rather than stored independently.
Example:
const fullName = firstName + " " + lastName;
Here, fullName is derived from firstName and lastName. Storing all three values in state creates duplicate sources of truth.
Computing during rendering:
- Keeps the value automatically synchronized.
- Avoids unnecessary effects and additional renders.
- Reduces state-management complexity.
- Prevents inconsistent combinations of state values.
Storing derived data separately may lead to stale or contradictory state if one source changes without updating the duplicate. Separate state is justified only when the value must vary independently, preserve a historical snapshot, or represent user-controlled data rather than a direct calculation. Expensive derived calculations may be memoized with useMemo, but memoization is a performance optimization rather than a correctness requirement.
Describe important best practices for managing state in React applications.
Important state-management practices include:
- Keep state minimal: Store only values that cannot be calculated from existing data.
- Use a single source of truth: Avoid duplicating the same information in multiple state variables.
- Place state appropriately: Keep state close to the components that use it, and lift it to a common parent only when sharing is required.
- Treat state as immutable: Create new objects and arrays instead of mutating existing ones.
- Use functional updates: Use updater functions when the next value depends on the previous value.
- Group related state carefully: Combine values that usually change together, but avoid one large object containing unrelated data.
- Avoid storing props in state: Do not copy props into state unless an intentional independent snapshot is required.
- Use meaningful initial values: Choose initial state that accurately represents the component's initial condition.
- Avoid unnecessary effects: Calculate synchronous derived values during rendering.
- Use reducers or external stores when justified: Complex transitions or widely shared data may require more structured state management.
What is the useEffect hook? Explain why side effects should be separated from the rendering process.
useEffect is a built-in React hook used to synchronize a component with systems outside React after a render is committed.
Typical side effects include:
- Fetching data from a server.
- Creating subscriptions.
- Starting or clearing timers.
- Interacting with browser APIs.
- Connecting to external services.
- Synchronizing non-React widgets.
Example:
useEffect(() => {
document.title = Count: ${count};
}, [count]);
Rendering should remain pure: given the same props and state, it should calculate the same JSX without modifying external systems. Performing side effects during rendering can cause duplicated operations, inconsistent output, and problems with interrupted or repeated renders. useEffect runs after React commits the rendered result, making it the proper place for external synchronization.
Compare the behavior of useEffect with no dependency array, an empty dependency array, and a dependency array containing values.
The dependency array controls when an effect must be synchronized again.
No dependency array:
useEffect(() => {
performTask();
});
The effect runs after every committed render.
Empty dependency array:
useEffect(() => {
performTask();
}, []);
The effect runs after the component is initially mounted. Its cleanup runs when the component unmounts. In development Strict Mode, React may run an additional setup-cleanup cycle to detect unsafe effects.
Dependency array containing values:
useEffect(() => {
performTask(userId);
}, [userId]);
The effect runs after the initial mount and again whenever userId changes according to Object.is comparison.
Every reactive value read by the effect, including relevant props, state, and component-scoped variables, should normally appear in the dependency array. Omitting dependencies can cause stale data and synchronization errors.
Explain effect cleanup in React. When is a cleanup function executed, and why is it necessary?
An effect may return a cleanup function that reverses or stops the work performed by the effect.
Example:
useEffect(() => {
const timerId = setInterval(updateClock, 1000);
return () => {
clearInterval(timerId);
};
}, []);
The cleanup function runs:
- Before the effect runs again because one of its dependencies changed.
- When the component is removed from the UI.
- During an additional setup-cleanup test cycle in development Strict Mode.
Cleanup is necessary for operations such as:
- Removing event listeners.
- Clearing timers.
- Unsubscribing from data sources.
- Closing network or socket connections.
- Cancelling or ignoring obsolete asynchronous work.
Without cleanup, an application may suffer from memory leaks, duplicate subscriptions, state updates from stale requests, and incorrect behavior after a component has disappeared.
Describe how asynchronous data fetching can be managed using useEffect. Discuss cleanup and race-condition handling.
Data fetching can be started inside an effect when it is required to synchronize a component with a remote data source.
Example:
useEffect(() => {
let ignore = false;
async function loadUser() {
setLoading(true);
try {
const response = await fetch(/api/users/${userId});
const data = await response.json();
if (!ignore) {
setUser(data);
}
} finally {
if (!ignore) {
setLoading(false);
}
}
}
loadUser();
return () => {
ignore = true;
};
}, [userId]);
The cleanup marks the previous request as obsolete. This prevents an older response from overwriting data produced by a newer request, which is a common race condition.
An AbortController may also be used to cancel supported requests. Real applications should additionally represent loading and error states. Framework-provided data-loading tools or dedicated query libraries are often preferable because they can provide caching, request deduplication, server rendering support, and structured race-condition handling.
Explain the purpose and operation of the useContext hook. Mention its benefits and performance considerations.
useContext allows a component to read and subscribe to a context value supplied by the nearest matching provider above it in the component tree.
Example:
const ThemeContext = createContext("light");
function Toolbar() {
const theme = useContext(ThemeContext);
return <div className={theme}>Toolbar</div>;
}
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
Benefits include:
- Avoiding repeated prop passing through intermediate components.
- Sharing values such as themes, authentication details, or locale settings.
- Allowing deeply nested consumers to access common data.
When the provider's value changes, React re-renders components consuming that context. Frequently creating a new object as the provider value can therefore cause extra renders. The value may be stabilized where appropriate, or large contexts may be separated into smaller contexts based on update frequency and responsibility.
Differentiate between useCallback and useMemo. Give suitable use cases for both hooks.
useCallback and useMemo both cache values between renders, but they cache different kinds of results.
useCallback:
const handleSave = useCallback(() => {
saveRecord(recordId);
}, [recordId]);
It caches a function reference. It is useful when passing a callback to a memoized child or when a stable function is required by another hook.
useMemo:
const visibleItems = useMemo(() => {
return filterItems(items, query);
}, [items, query]);
It caches the result of a calculation. It is useful for expensive calculations or for stabilizing an object or array passed to a memoized component.
Conceptually, caching a callback is similar to caching the function itself rather than calling it. Both hooks require correct dependency arrays. They should be used only when they solve a measured or likely performance problem because they introduce comparison work and additional code complexity. Application correctness must not depend on memoization.
Discuss React performance optimization with reference to re-renders, React.memo, useCallback, and useMemo.
React performance optimization should begin by identifying an actual rendering cost through profiling rather than adding memoization everywhere.
Key techniques include:
- Keep state local: Prevent unrelated parts of the tree from rendering when state changes.
- Use pure rendering logic: Components should produce output solely from props, state, and context.
- Use
React.memo: Allow a component to skip rendering when its props are unchanged. - Use
useCallback: Preserve callback identity when passing functions to memoized children. - Use
useMemo: Cache expensive calculations or stabilize derived object and array references. - Avoid unnecessary effects: Effects that repeatedly update state can create render loops.
- Split context carefully: A single frequently changing context can re-render many consumers.
Memoization is ineffective if a component always receives newly created objects or functions. It also has costs: dependencies must be compared, cached values must be retained, and code becomes more complex. Therefore, optimization should target expensive or frequently repeated work and should be validated with tools such as the React Profiler.
What is a custom hook in React? Explain how custom hooks support logic reuse, with an example.
A custom hook is a JavaScript function whose name begins with use and which may call React hooks. It extracts reusable stateful or effect-based logic without duplicating component code.
Example:
function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => {
function handleOnline() {
setIsOnline(true);
}
function handleOffline() {
setIsOnline(false);
}
window.addEventListener("online", handleOnline);
window.addEventListener("offline", handleOffline);
return () => {
window.removeEventListener("online", handleOnline);
window.removeEventListener("offline", handleOffline);
};
}, []);
return isOnline;
}
A component can call const isOnline = useOnlineStatus(); to use the logic. Custom hooks reuse logic, not the same state instance. Every component calling the hook receives its own state and effects unless the hook connects to a shared external source.
State and explain the Rules of Hooks. Why must hooks be called in a consistent order?
The Rules of Hooks are:
- Call hooks only at the top level. Hooks must not be called inside conditions, loops, nested functions, event handlers, or
tryblocks. - Call hooks only from React functions. They may be called from function components or custom hooks, not from ordinary JavaScript functions.
Correct usage:
function Profile({ enabled }) {
const [name, setName] = useState("");
useEffect(() => {
if (enabled) {
console.log("Enabled");
}
}, [enabled]);
return <p>{name}</p>;
}
React associates hook state with the order in which hooks are called. If a hook is skipped conditionally, later hooks may shift positions, causing React to connect values and effects to the wrong calls. Conditions should therefore be placed inside a hook's callback or handled through conditional rendering of a separate component. The hooks ESLint plugin can detect many rule violations and dependency problems.
Identify and explain common pitfalls and anti-patterns associated with useEffect and its dependency array.
Common useEffect pitfalls include:
- Missing dependencies: The effect may read stale props or state because it does not rerun when those values change.
- Unstable dependencies: Newly created objects or functions may cause the effect to run after every render.
- State-update loops: An effect updates state that is also one of its changing dependencies, producing repeated renders.
- Using effects for derived values: Calculating synchronous values in an effect adds unnecessary state and an extra render.
- Missing cleanup: Timers, listeners, subscriptions, or requests may remain active.
- Making the effect callback directly
async: An effect must return either nothing or a cleanup function, not a Promise. An inner asynchronous function should be created instead. - Suppressing dependency warnings: Disabling the linter hides synchronization errors instead of solving them.
- Using an empty array to force mount-only behavior: This is incorrect if the effect reads reactive values that may change.
The correct approach is to include all reactive dependencies and restructure the code when this causes unwanted executions. Possible solutions include moving non-reactive values outside the component, moving event-specific logic into event handlers, or removing an unnecessary effect.
Analyze the following React code and explain why it may produce incorrect or inefficient behavior. Suggest an improved approach.
function Search({ items }) {
const [query, setQuery] = useState("");
const [results, setResults] = useState([]);
useEffect(() => {
setResults(items.filter(item => item.includes(query)));
}, [query]);
return <ResultList results={results} />;
} The code has two main problems.
- The effect reads
itemsbut does not list it as a dependency. Ifitemschanges whilequeryremains unchanged,resultsbecomes stale. resultsis derived entirely fromitemsandquery. Storing it as separate state creates a duplicate source of truth and causes an additional render after the effect updates the state.
For an inexpensive calculation, compute the result during rendering:
function Search({ items }) {
const [query, setQuery] = useState("");
const results = items.filter(item => item.includes(query));
return <ResultList results={results} />;
}
If filtering is measurably expensive, useMemo may be used:
const results = useMemo(
() => items.filter(item => item.includes(query)),
[items, query]
);
This version remains synchronized with both inputs and avoids an effect-driven state update. useMemo should be added for performance only when the calculation or child-rendering cost justifies it.
Explain the event handling mechanism in React. How does it differ from event handling in traditional HTML?
React handles user interactions through event handlers passed to JSX elements as props.
- React event names use camelCase, such as
onClick,onChange, andonSubmit. - An event handler receives a function rather than a string containing JavaScript code.
- React provides a cross-browser event wrapper known as a SyntheticEvent.
- The event object provides methods such as
preventDefault()andstopPropagation().
Example:
function Button() {
function handleClick(event) {
console.log("Button clicked");
}
return <button onClick={handleClick}>Click</button>;
}
In traditional HTML, an event may be written as onclick="handleClick()", whereas React uses onClick={handleClick}. React's approach keeps event logic within JavaScript functions and integrates it with the component model.
Did this save you a night before the exam?
LPU Notes is free, and it stays free. Ads cover part of the server bill. The rest comes out of a student's own pocket: the domain, the storage, and keeping the site up through the weeks everyone needs it at once.
The payment button didn't load. An ad blocker or a filtered network is the usual reason. to try again.
Nothing here is ever locked, and nothing unlocks. Chip in only if it was worth it. What it pays for →