Thuta Learning
ExercisesWeb Developmentintermediate

Practice: Hooks & Data

Relax. We'll talk through this in plain words — no textbook voice.

What you'll walk away with

  • Practice Hooks & Data on your own
  • Reinforce the skills you've already learned through practice
  • Get comfortable finding bugs, fixing them, and checking your own work

Let's think about this for a second

This lesson is a step harder than Part 1, combining concepts you learned in the Intermediate/Advanced chapter — useEffect, useContext, custom hooks, data fetching, and React Router. Each task is meant to give you hands-on practice with patterns you'll run into all the time in real-world apps: pulling data from an API, sharing a theme across an entire app, extracting logic into a custom hook, and reading a URL param when navigating between pages. As you work through these tasks yourself, you'll also get to review how to combine them with the concepts from the Basic chapter.

Practice Exercises

Task 1: Use useEffect to fetch the user list from fetch("https://jsonplaceholder.typicode.com/users") and render it along with a loading state. Task 2: Build a ThemeContext (light/dark) with createContext, and wire it up so two levels of nested components can read the theme value with useContext. Task 3: Build a custom hook called useToggle(initialValue) that returns a value and a toggle function (so it can be used for things like opening/closing a modal or showing/hiding a sidebar). Task 4: Use react-router-dom to set up a /users/:id route, read the id with useParams, and display it on screen.

Code Example

javascript
// Task 1 starter - UserList.jsx
import { useState, useEffect } from "react";

function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);

  // TODO: fetch users inside useEffect, then setUsers + setLoading(false)

  if (loading) return <p>Loading...</p>;
  return (
    <ul>
      {users.map((u) => (
        <li key={u.id}>{u.name}</li>
      ))}
    </ul>
  );
}

export default UserList;

// Task 3 starter - useToggle.js
import { useState } from "react";

function useToggle(initialValue = false) {
  const [value, setValue] = useState(initialValue);
  // TODO: return [value, toggle] where toggle flips the boolean
}

export default useToggle;

// Task 4 starter - UserDetail.jsx (used at route /users/:id)
import { useParams } from "react-router-dom";

function UserDetail() {
  // TODO: read id with useParams and display it
  return <h3>User Detail</h3>;
}

export default UserDetail;
You should see
Once all 4 tasks are done: a user list appears from API data, the theme context reaches down to nested components, the toggle hook is shared between 2 components, and the user detail changes based on the URL param.

Try it in 5 minutes

Add an error state to Task 1's UserList — show a "Failed to load users" message if the fetch fails (call setError(true) inside the catch block) — this should only take about 5 minutes.

A quick word of caution

Instead of using an async function directly inside useEffect, you need to define a separate async function inside it and call it immediately (the IIFE pattern) — you'll use this a lot with fetch APIs.

Easy traps

  • Writing useEffect(async () => {...}) — declaring the fetch directly as an async function inside useEffect — since useEffect's callback can't return a promise
  • Assuming state inside a custom hook is automatically shared when two components call it, not realizing each component instance gets its own separate state unless you use Context

Now Try It Yourself

Add an error state to Task 1's UserList — show a "Failed to load users" message if the fetch fails (call setError(true) inside the catch block) — this should only take about 5 minutes.

You'll know it worked when: Once all 4 tasks are done: a user list appears from API data, the theme context reaches down to nested components, the toggle hook is shared between 2 components, and the user detail changes based on the URL param.

Practice: Hooks & Data | Thuta Learning