CPCODELAB
Practice

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
Score: 0 / 18
0/18 answered
  1. 1

    What is the correct way to embed a JavaScript expression inside JSX?

  2. 2

    Which of the following is a valid React functional component?

  3. 3

    What does useState return?

  4. 4

    Which hook should you use to run a side effect after every render?

  5. 5

    What is the purpose of the key prop when rendering a list?

  6. 6

    What happens when you call a state setter function returned by useState?

  7. 7

    How do you pass data from a parent component to a child component?

  8. 8

    Given const [show, setShow] = useState(true), what does the following render? return show && <p>Hello</p>;

  9. 9

    What is a controlled input in React?

  10. 10

    What is the correct dependency array to make a useEffect run only once, on mount?

  11. 11

    Which of the following correctly calls a function when a button is clicked?

  12. 12

    What is the primary purpose of useContext?

  13. 13

    What does useRef return, and does changing its .current property cause a re-render?

  14. 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. 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. 16

    What is React.lazy used for?

  17. 17

    Given const double = useMemo(() => value * 2, [value]), when will double be recalculated?

  18. 18

    What is the difference between useCallback(fn, deps) and useMemo(() => fn, deps)?

🛠️

Coding exercises

9 tasks
1

useRef Previous Value Tracker

Medium

Write 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.

Starter
jsx
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
jsx
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>
  );
}
2

Cart Reducer

Medium

Write 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.

Starter
jsx
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
jsx
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>
  );
}
3

Lazy-Loaded Settings Panel

Hard

Create 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
jsx
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>
  );
}
4

Greeting Card Component

Easy

Write 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.

Starter
jsx
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
jsx
export function GreetingCard({ name, age }) {
  return (
    <div>
      <h2>Hello, {name}!</h2>
      <p>You are {age} years old.</p>
    </div>
  );
}
5

Toggle Button

Easy

Write 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.

Starter
jsx
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
jsx
import { useState } from "react";

export function ToggleButton() {
  const [on, setOn] = useState(false);

  return (
    <button onClick={() => setOn(!on)}>
      {on ? "ON" : "OFF"}
    </button>
  );
}
6

Fruit List with Keys

Easy

Write 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.

Starter
jsx
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
jsx
export function FruitList({ fruits }) {
  return (
    <ul>
      {fruits.map((fruit) => (
        <li key={fruit}>{fruit}</li>
      ))}
    </ul>
  );
}
7

Controlled Search Input

Medium

Write 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.

Starter
jsx
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
jsx
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>
  );
}
8

Data Fetcher with useEffect

Medium

Write 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>.

Starter
jsx
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
jsx
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>
  );
}
9

Custom useLocalStorage Hook

Hard

Write 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
jsx
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>
  );
}