Practice React
You’ve read the lessons — now prove it. Take the quiz for instant feedback, then work the coding exercises. Try each one yourself before you reveal the solution.
Quick quiz
18 questions- 1
What is the correct way to embed a JavaScript expression inside JSX?
- 2
Which of the following is a valid React functional component?
- 3
What does
useStatereturn? - 4
Which hook should you use to run a side effect after every render?
- 5
What is the purpose of the
keyprop when rendering a list? - 6
What happens when you call a state setter function returned by
useState? - 7
How do you pass data from a parent component to a child component?
- 8
Given
const [show, setShow] = useState(true), what does the following render?return show && <p>Hello</p>; - 9
What is a controlled input in React?
- 10
What is the correct dependency array to make a
useEffectrun only once, on mount? - 11
Which of the following correctly calls a function when a button is clicked?
- 12
What is the primary purpose of
useContext? - 13
What does
useRefreturn, and does changing its.currentproperty cause a re-render? - 14
You want to dispatch an action
{ type: "reset" }to restore a component to its initial state. Which hook is the best fit for this pattern? - 15
Which API do you use to render a React subtree into a DOM node that is outside the component's parent DOM hierarchy?
- 16
What is
React.lazyused for? - 17
Given
const double = useMemo(() => value * 2, [value]), when willdoublebe recalculated? - 18
What is the difference between
useCallback(fn, deps)anduseMemo(() => fn, deps)?
Coding exercises
9 tasksuseRef Previous Value Tracker
MediumWrite a component called PrevValue that accepts a prop value (number). Display both the current and previous value. The previous value should persist across renders but never cause extra re-renders. Use useRef to track the previous value.
import { useRef, useEffect } from "react";
export function PrevValue({ value }) {
// your code here
}💡 Show hint
Store prevRef.current in a local variable before the effect updates it. The effect should run after every render (no dependency array) to update prevRef.current = value.
✅ Show solution
import { useRef, useEffect } from "react";
export function PrevValue({ value }) {
const prevRef = useRef(value);
useEffect(() => {
prevRef.current = value;
});
const prev = prevRef.current;
return (
<div>
<p>Current: {value}</p>
<p>Previous: {prev}</p>
</div>
);
}Cart Reducer
MediumWrite a cartReducer(state, action) function and a ShoppingCart component that uses it with useReducer. State shape: { items: Array<{ id, name, qty }> }. Support three actions: "add" (adds item or increments qty), "remove" (removes item by id), "clear" (empties cart). Render the item list and total quantity.
import { useReducer } from "react";
function cartReducer(state, action) {
// your code here
}
const initialState = { items: [] };
export function ShoppingCart() {
const [cart, dispatch] = useReducer(cartReducer, initialState);
// your code here
}💡 Show hint
For the "add" case, check if the item already exists with .find(). If it does, map over items and increment qty; otherwise append the new item with qty: 1.
✅ Show solution
import { useReducer } from "react";
function cartReducer(state, action) {
switch (action.type) {
case "add": {
const existing = state.items.find(i => i.id === action.item.id);
if (existing) {
return {
items: state.items.map(i =>
i.id === action.item.id ? { ...i, qty: i.qty + 1 } : i
)
};
}
return { items: [...state.items, { ...action.item, qty: 1 }] };
}
case "remove":
return { items: state.items.filter(i => i.id !== action.id) };
case "clear":
return { items: [] };
default:
throw new Error("Unknown action: " + action.type);
}
}
const initialState = { items: [] };
const SAMPLE = [
{ id: 1, name: "React Book" },
{ id: 2, name: "TypeScript Guide" },
];
export function ShoppingCart() {
const [cart, dispatch] = useReducer(cartReducer, initialState);
const total = cart.items.reduce((sum, i) => sum + i.qty, 0);
return (
<div>
<h2>Cart ({total} items)</h2>
<ul>
{cart.items.map(i => (
<li key={i.id}>
{i.name} x{i.qty}
<button onClick={() => dispatch({ type: "remove", id: i.id })}>Remove</button>
</li>
))}
</ul>
{SAMPLE.map(p => (
<button key={p.id} onClick={() => dispatch({ type: "add", item: p })}>
Add {p.name}
</button>
))}
<button onClick={() => dispatch({ type: "clear" })}>Clear cart</button>
</div>
);
}Lazy-Loaded Settings Panel
HardCreate a component called AppShell that has a button labelled "Open Settings". When clicked, it should dynamically import and render a SettingsPanel component (write a simple one inline for this exercise — it just renders <div>Settings Panel</div>). While SettingsPanel is loading, show a <p>Loading settings...</p> fallback. Use React.lazy and <Suspense>.
💡 Show hint
Use const SettingsPanel = lazy(() => import('./SettingsPanel')) (or a mock dynamic import that resolves immediately). Wrap the conditional render in <Suspense fallback={...}>.
✅ Show solution
import { lazy, Suspense, useState } from "react";
// In a real project this would be a separate file import
const SettingsPanel = lazy(() =>
Promise.resolve({ default: function SettingsPanel() {
return <div>Settings Panel</div>;
}})
);
export function AppShell() {
const [open, setOpen] = useState(false);
return (
<div>
<button onClick={() => setOpen(o => !o)}>
{open ? "Close Settings" : "Open Settings"}
</button>
{open && (
<Suspense fallback={<p>Loading settings...</p>}>
<SettingsPanel />
</Suspense>
)}
</div>
);
}Greeting Card Component
EasyWrite a functional component called GreetingCard that accepts two props: name (string) and age (number). It should render a <div> containing an <h2> with Hello, {name}! and a <p> with You are {age} years old.
export function GreetingCard({ name, age }) {
// your code here
}💡 Show hint
Access props directly via destructuring in the function parameters and embed them with { } in JSX.
✅ Show solution
export function GreetingCard({ name, age }) {
return (
<div>
<h2>Hello, {name}!</h2>
<p>You are {age} years old.</p>
</div>
);
}Toggle Button
EasyWrite a component called ToggleButton. It should maintain a boolean state called on (initially false). Render a <button> that shows the text "ON" when on is true and "OFF" when on is false. Clicking the button should toggle the state.
import { useState } from "react";
export function ToggleButton() {
// your code here
}💡 Show hint
Use useState(false) and set the new value to !on inside the click handler.
✅ Show solution
import { useState } from "react";
export function ToggleButton() {
const [on, setOn] = useState(false);
return (
<button onClick={() => setOn(!on)}>
{on ? "ON" : "OFF"}
</button>
);
}Fruit List with Keys
EasyWrite a component called FruitList that accepts a prop fruits (an array of strings). Render an unordered list (<ul>) where each fruit is a <li>. Make sure each <li> has a unique key prop.
export function FruitList({ fruits }) {
// your code here
}💡 Show hint
Use fruits.map() inside the JSX return. Use the index or, better, the fruit string itself as the key when items are unique.
✅ Show solution
export function FruitList({ fruits }) {
return (
<ul>
{fruits.map((fruit) => (
<li key={fruit}>{fruit}</li>
))}
</ul>
);
}Controlled Search Input
MediumWrite a component called SearchBox. It should have a controlled <input> (type text) whose value is stored in state. Below the input, display a <p> that shows: Searching for: {query} where query is the current input value. The display should update live as the user types.
import { useState } from "react";
export function SearchBox() {
// your code here
}💡 Show hint
Create state with useState(""), bind value={query} on the input, and update state with onChange={(e) => setQuery(e.target.value)}.
✅ Show solution
import { useState } from "react";
export function SearchBox() {
const [query, setQuery] = useState("");
return (
<div>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search..."
/>
<p>Searching for: {query}</p>
</div>
);
}Data Fetcher with useEffect
MediumWrite a component called UserCard that accepts a prop userId (number). On mount (and whenever userId changes) it should fetch https://jsonplaceholder.typicode.com/users/{userId} and store the result in state. While loading, show <p>Loading...</p>. Once loaded, show the user's name and email inside a <div>.
import { useState, useEffect } from "react";
export function UserCard({ userId }) {
// your code here
}💡 Show hint
Put the fetch inside useEffect with [userId] as the dependency array. Use a separate loading state boolean.
✅ Show solution
import { useState, useEffect } from "react";
export function UserCard({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
setLoading(true);
fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
.then((res) => res.json())
.then((data) => {
setUser(data);
setLoading(false);
});
}, [userId]);
if (loading) return <p>Loading...</p>;
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}Custom useLocalStorage Hook
HardWrite a custom hook called useLocalStorage that works like useState but persists the value in localStorage. It should accept key (string) and initialValue (any) as arguments. It must: (1) initialise from localStorage if a value exists for that key, otherwise use initialValue; (2) return [storedValue, setValue] where setValue updates both state and localStorage. Then write a small demo component ColorPicker that uses useLocalStorage("color", "red") to persist a color preference across page reloads, showing an <input type="color"> and the current hex value.
💡 Show hint
Wrap the localStorage.getItem call in a try/catch. In setValue, call localStorage.setItem before calling the React state setter.
✅ Show solution
import { useState } from "react";
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = localStorage.getItem(key);
return item !== null ? JSON.parse(item) : initialValue;
} catch {
return initialValue;
}
});
const setValue = (value) => {
try {
const valueToStore =
value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(error);
}
};
return [storedValue, setValue];
}
export function ColorPicker() {
const [color, setColor] = useLocalStorage("color", "#ff0000");
return (
<div>
<label htmlFor="color">Pick a colour:</label>
<input
id="color"
type="color"
value={color}
onChange={(e) => setColor(e.target.value)}
/>
<p>Current colour: <code>{color}</code></p>
</div>
);
}