npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

vite-react-kek

v1.0.0

Published

* JSX and component thinking * Props * State * `useState` * Conditional rendering * List rendering * Event handling * Controlled interaction patterns * Derived UI * Lifting state up * `useEffect` * `useRef` * `useMemo` * `useCallback` * Cont

Readme

  • JSX and component thinking
  • Props
  • State
  • useState
  • Conditional rendering
  • List rendering
  • Event handling
  • Controlled interaction patterns
  • Derived UI
  • Lifting state up
  • useEffect
  • useRef
  • useMemo
  • useCallback
  • Context API
  • Component composition
  • Reusable child components
  • Dynamic UI behavior
  • Basic optimization thinking

Problem 1: Email Inbox Category Switcher

Problem Statement

You are building a simple Inbox Category Switcher for an email application. The inbox is divided into three categories:

  • Primary
  • Promotions
  • Updates

Users should be able to click on a category tab and see only the emails that belong to that category. The selected category should be visually highlighted so that users know which section they are viewing.

This is a realistic feature because almost every email product, productivity dashboard, or messaging tool has some kind of categorized view. The goal of this problem is to help students practice state-driven filtering, list rendering, conditional styling, and reusable component thinking in React.

Functional Requirements

  1. Show three category buttons:

    • Primary
    • Promotions
    • Updates
  2. Maintain a list of emails, where each email contains:

    • id
    • subject
    • sender
    • category
  3. By default, show emails from the Primary category.

  4. Clicking a category button should:

    • update the active category
    • re-render the visible emails
  5. The active category button should look visually different.

  6. Each visible email should show:

    • subject
    • sender
  7. If a category contains no emails, show:

    • No emails in this category
  8. Use a child component to render each email item.

Concepts Covered

  • State
  • Props
  • useState
  • Conditional rendering
  • List rendering
  • Derived UI

Hints

  1. Keep the currently active category in state.
  2. Use filter() to derive the visible emails.
  3. Pass email data as props into a reusable child component.
  4. Active button styling should depend on the selected category.
  5. This is a great example of deriving UI from state rather than storing duplicate values.

Code Stub

import React, { useState } from "react";

const emails = [
  { id: 1, subject: "Project kickoff scheduled", sender: "Manager", category: "Primary" },
  { id: 2, subject: "50% off on travel bags", sender: "ShopEase", category: "Promotions" },
  { id: 3, subject: "Your monthly usage report", sender: "CloudPanel", category: "Updates" },
  { id: 4, subject: "Interview round details", sender: "Recruiter", category: "Primary" },
];

function EmailItem({ email }) {
  return (
    <div style={{ border: "1px solid #ddd", padding: "10px", marginBottom: "10px" }}>
      <h4>{email.subject}</h4>
      <p>From: {email.sender}</p>
    </div>
  );
}

export default function InboxCategorySwitcher() {
  const [activeCategory, setActiveCategory] = useState("Primary");

  // TODO: derive visible emails
  const visibleEmails = emails;

  return (
    <div style={{ padding: "20px" }}>
      <h2>Email Inbox</h2>

      <button onClick={() => setActiveCategory("Primary")}>Primary</button>
      <button onClick={() => setActiveCategory("Promotions")} style={{ marginLeft: "10px" }}>
        Promotions
      </button>
      <button onClick={() => setActiveCategory("Updates")} style={{ marginLeft: "10px" }}>
        Updates
      </button>

      <div style={{ marginTop: "20px" }}>
        {visibleEmails.map((email) => (
          <EmailItem key={email.id} email={email} />
        ))}
      </div>
    </div>
  );
}

Full Solution

import React, { useState } from "react";

const emails = [
  { id: 1, subject: "Project kickoff scheduled", sender: "Manager", category: "Primary" },
  { id: 2, subject: "50% off on travel bags", sender: "ShopEase", category: "Promotions" },
  { id: 3, subject: "Your monthly usage report", sender: "CloudPanel", category: "Updates" },
  { id: 4, subject: "Interview round details", sender: "Recruiter", category: "Primary" },
];

function EmailItem({ email }) {
  return (
    <div style={{ border: "1px solid #ddd", padding: "10px", marginBottom: "10px", borderRadius: "8px" }}>
      <h4>{email.subject}</h4>
      <p>From: {email.sender}</p>
    </div>
  );
}

export default function InboxCategorySwitcher() {
  const [activeCategory, setActiveCategory] = useState("Primary");

  const visibleEmails = emails.filter(
    (email) => email.category === activeCategory
  );

  const getButtonStyle = (category) => ({
    backgroundColor: activeCategory === category ? "#ddd" : "white",
    padding: "8px 12px",
  });

  return (
    <div style={{ padding: "20px" }}>
      <h2>Email Inbox</h2>

      <button
        onClick={() => setActiveCategory("Primary")}
        style={getButtonStyle("Primary")}
      >
        Primary
      </button>
      <button
        onClick={() => setActiveCategory("Promotions")}
        style={{ ...getButtonStyle("Promotions"), marginLeft: "10px" }}
      >
        Promotions
      </button>
      <button
        onClick={() => setActiveCategory("Updates")}
        style={{ ...getButtonStyle("Updates"), marginLeft: "10px" }}
      >
        Updates
      </button>

      <div style={{ marginTop: "20px" }}>
        {visibleEmails.length === 0 ? (
          <p>No emails in this category</p>
        ) : (
          visibleEmails.map((email) => <EmailItem key={email.id} email={email} />)
        )}
      </div>
    </div>
  );
}

Test Cases

  1. Action: App loads Expected: Only Primary emails are shown.

  2. Action: User clicks Promotions Expected: Only promotion emails are displayed.

  3. Action: User clicks Updates Expected: Only update emails are displayed.

  4. Action: User switches back to Primary Expected: Primary emails appear again.

  5. Action: Active category is Promotions Expected: Promotions button is visually highlighted.

  6. Action: Active category changes Expected: Previous active button loses highlight.

  7. Action: Category contains no emails Expected: No emails in this category is shown.

  8. Action: Each email item renders Expected: Subject and sender are both visible.

  9. Action: User switches categories multiple times Expected: List always matches the selected category.

  10. Action: App renders email items Expected: Each email uses its id as React key.


Problem 2: Playlist Song Highlighter

Problem Statement

You are building a Playlist Song Highlighter for a music streaming application. Users should be able to click on a song from the playlist and see which track is currently selected.

The selected song should be highlighted in the list, and a details panel should show the currently selected song’s title and artist. This kind of interaction is very common in media applications, music players, podcast platforms, and video playlists.

This problem helps students practice state, click interaction, selected-item rendering, props, and dynamic styling.

Functional Requirements

  1. Display a list of songs.

  2. Each song should include:

    • id
    • title
    • artist
  3. The first song should be selected by default.

  4. Clicking a song should:

    • mark it as selected
    • update the details panel
  5. The selected song should be styled differently.

  6. The details panel should show:

    • selected song title
    • selected song artist
  7. Use a child component to render each song row.

Concepts Covered

  • State
  • Props
  • useState
  • Dynamic rendering
  • Conditional styling
  • List rendering

Hints

  1. Store the selected song’s id in state instead of storing the whole object.
  2. Use find() to get the selected song data.
  3. Pass isSelected as a prop to the child component.
  4. The click handler should update only the selected ID.
  5. This is a common “master-detail” UI pattern.

Code Stub

import React, { useState } from "react";

const songs = [
  { id: 1, title: "Blinding Lights", artist: "The Weeknd" },
  { id: 2, title: "Levitating", artist: "Dua Lipa" },
  { id: 3, title: "Heat Waves", artist: "Glass Animals" },
];

function SongItem({ song, isSelected, onSelect }) {
  return (
    <div
      onClick={() => onSelect(song.id)}
      style={{
        padding: "10px",
        border: "1px solid #ddd",
        marginBottom: "8px",
        cursor: "pointer",
      }}
    >
      <strong>{song.title}</strong>
      <p>{song.artist}</p>
    </div>
  );
}

export default function PlaylistHighlighter() {
  const [selectedSongId, setSelectedSongId] = useState(1);

  // TODO: derive selected song
  const selectedSong = songs[0];

  return (
    <div style={{ padding: "20px" }}>
      <h2>Playlist</h2>

      <div>
        {songs.map((song) => (
          <SongItem
            key={song.id}
            song={song}
            isSelected={false}
            onSelect={setSelectedSongId}
          />
        ))}
      </div>

      <div style={{ marginTop: "20px" }}>
        <h3>Now Selected</h3>
        <p>Title: {selectedSong.title}</p>
        <p>Artist: {selectedSong.artist}</p>
      </div>
    </div>
  );
}

Full Solution

import React, { useState } from "react";

const songs = [
  { id: 1, title: "Blinding Lights", artist: "The Weeknd" },
  { id: 2, title: "Levitating", artist: "Dua Lipa" },
  { id: 3, title: "Heat Waves", artist: "Glass Animals" },
];

function SongItem({ song, isSelected, onSelect }) {
  return (
    <div
      onClick={() => onSelect(song.id)}
      style={{
        padding: "10px",
        border: "1px solid #ddd",
        marginBottom: "8px",
        cursor: "pointer",
        backgroundColor: isSelected ? "#e6f0ff" : "white",
      }}
    >
      <strong>{song.title}</strong>
      <p>{song.artist}</p>
    </div>
  );
}

export default function PlaylistHighlighter() {
  const [selectedSongId, setSelectedSongId] = useState(1);

  const selectedSong = songs.find((song) => song.id === selectedSongId);

  return (
    <div style={{ padding: "20px" }}>
      <h2>Playlist</h2>

      <div>
        {songs.map((song) => (
          <SongItem
            key={song.id}
            song={song}
            isSelected={selectedSongId === song.id}
            onSelect={setSelectedSongId}
          />
        ))}
      </div>

      <div style={{ marginTop: "20px" }}>
        <h3>Now Selected</h3>
        <p>Title: {selectedSong.title}</p>
        <p>Artist: {selectedSong.artist}</p>
      </div>
    </div>
  );
}

Test Cases

  1. Action: App loads Expected: First song is selected by default.

  2. Action: User clicks second song Expected: Second song becomes selected.

  3. Action: User clicks third song Expected: Third song becomes selected.

  4. Action: Selected song changes Expected: Details panel updates to the correct title and artist.

  5. Action: A song is selected Expected: That row is visually highlighted.

  6. Action: User clicks the same song again Expected: It stays selected.

  7. Action: User switches between songs repeatedly Expected: Highlight and details panel always remain synchronized.

  8. Action: Song rows render Expected: Each row shows title and artist.

  9. Action: App first renders Expected: Details panel matches the first song.

  10. Action: User selects another song Expected: Previous song loses highlight.


Problem 3: Job Application Status Board

Problem Statement

You are building a Job Application Status Board for a career portal. Users should be able to view job applications and filter them by their status:

  • Applied
  • Interview
  • Rejected

This kind of feature is commonly seen in job boards, hiring dashboards, and tracking applications. The UI should allow users to switch views and inspect only the subset they need at the moment.

This problem helps students practice filtering, state-driven UI, button-based view switching, and rendering lists of objects.

Functional Requirements

  1. Display a list of job applications.

  2. Each application should include:

    • id
    • company
    • role
    • status
  3. Add four filter buttons:

    • All
    • Applied
    • Interview
    • Rejected
  4. By default, show all applications.

  5. Clicking a filter button should show only matching applications.

  6. Highlight the active filter button.

  7. If no applications match the current filter, show:

    • No applications found
  8. Show the number of visible applications.

Concepts Covered

  • State
  • useState
  • Conditional rendering
  • List filtering
  • Derived UI

Hints

  1. Store the active filter in state.
  2. Use filter() to compute the visible applications.
  3. The All filter is just a special case.
  4. Visible count should come from the filtered array.
  5. Button styles should depend on active state.

Code Stub

import React, { useState } from "react";

const applications = [
  { id: 1, company: "Google", role: "Frontend Engineer", status: "Applied" },
  { id: 2, company: "Amazon", role: "SDE 1", status: "Interview" },
  { id: 3, company: "Meta", role: "UI Engineer", status: "Rejected" },
  { id: 4, company: "Atlassian", role: "React Developer", status: "Applied" },
];

export default function JobStatusBoard() {
  const [activeFilter, setActiveFilter] = useState("All");

  // TODO: derive visible applications
  const visibleApplications = applications;

  return (
    <div style={{ padding: "20px" }}>
      <h2>Job Application Status Board</h2>

      <button onClick={() => setActiveFilter("All")}>All</button>
      <button onClick={() => setActiveFilter("Applied")} style={{ marginLeft: "10px" }}>
        Applied
      </button>
      <button onClick={() => setActiveFilter("Interview")} style={{ marginLeft: "10px" }}>
        Interview
      </button>
      <button onClick={() => setActiveFilter("Rejected")} style={{ marginLeft: "10px" }}>
        Rejected
      </button>

      <p style={{ marginTop: "20px" }}>Visible Applications: {visibleApplications.length}</p>

      {visibleApplications.map((application) => (
        <div key={application.id} style={{ border: "1px solid #ddd", padding: "10px", marginBottom: "10px" }}>
          <h4>{application.company}</h4>
          <p>{application.role}</p>
          <p>Status: {application.status}</p>
        </div>
      ))}
    </div>
  );
}

Full Solution

import React, { useState } from "react";

const applications = [
  { id: 1, company: "Google", role: "Frontend Engineer", status: "Applied" },
  { id: 2, company: "Amazon", role: "SDE 1", status: "Interview" },
  { id: 3, company: "Meta", role: "UI Engineer", status: "Rejected" },
  { id: 4, company: "Atlassian", role: "React Developer", status: "Applied" },
];

export default function JobStatusBoard() {
  const [activeFilter, setActiveFilter] = useState("All");

  const visibleApplications =
    activeFilter === "All"
      ? applications
      : applications.filter((application) => application.status === activeFilter);

  const getButtonStyle = (filter) => ({
    backgroundColor: activeFilter === filter ? "#ddd" : "white",
    padding: "8px 12px",
  });

  return (
    <div style={{ padding: "20px" }}>
      <h2>Job Application Status Board</h2>

      <button onClick={() => setActiveFilter("All")} style={getButtonStyle("All")}>
        All
      </button>
      <button
        onClick={() => setActiveFilter("Applied")}
        style={{ ...getButtonStyle("Applied"), marginLeft: "10px" }}
      >
        Applied
      </button>
      <button
        onClick={() => setActiveFilter("Interview")}
        style={{ ...getButtonStyle("Interview"), marginLeft: "10px" }}
      >
        Interview
      </button>
      <button
        onClick={() => setActiveFilter("Rejected")}
        style={{ ...getButtonStyle("Rejected"), marginLeft: "10px" }}
      >
        Rejected
      </button>

      <p style={{ marginTop: "20px" }}>
        Visible Applications: {visibleApplications.length}
      </p>

      {visibleApplications.length === 0 ? (
        <p>No applications found</p>
      ) : (
        visibleApplications.map((application) => (
          <div
            key={application.id}
            style={{ border: "1px solid #ddd", padding: "10px", marginBottom: "10px", borderRadius: "8px" }}
          >
            <h4>{application.company}</h4>
            <p>{application.role}</p>
            <p>Status: {application.status}</p>
          </div>
        ))
      )}
    </div>
  );
}

Test Cases

  1. Action: App loads Expected: All applications are visible.

  2. Action: User clicks Applied Expected: Only applied jobs are shown.

  3. Action: User clicks Interview Expected: Only interview jobs are shown.

  4. Action: User clicks Rejected Expected: Only rejected jobs are shown.

  5. Action: User clicks All Expected: Full application list is visible again.

  6. Action: Active filter changes Expected: Correct button is highlighted.

  7. Action: Visible applications are filtered Expected: Count updates to match rendered cards.

  8. Action: No items match a filter Expected: No applications found is shown.

  9. Action: User switches filters multiple times Expected: UI always remains accurate.

  10. Action: Each application card renders Expected: Company, role, and status are all visible.


Problem 4: Product Image Color Preview

Problem Statement

You are building a Product Color Preview widget for an ecommerce product page. A product is available in multiple color options, and when a user clicks on a color swatch, the product preview should update to show the selected variant.

This kind of interaction is extremely common in ecommerce applications where customers compare visual variants before making a purchase decision.

The purpose of this problem is to help students practice selected state, dynamic rendering, conditional styling, and props-based component design.

Functional Requirements

  1. Show a product title.

  2. Show a main preview image for the selected color variant.

  3. Provide a set of color option buttons.

  4. Each color option should contain:

    • id
    • colorName
    • image
  5. The first color should be selected by default.

  6. Clicking a color option should:

    • update the preview image
    • highlight the selected option
  7. Show the selected color name below the image.

  8. Use a child component for the color option item.

Concepts Covered

  • State
  • Props
  • useState
  • Dynamic rendering
  • Conditional styling
  • Reusable child components

Hints

  1. Store the selected color ID in state.
  2. Use find() to get the selected color object.
  3. Pass selected state into the child swatch component.
  4. The main image should depend entirely on selected state.
  5. This is another good example of a master-detail UI.

Code Stub

import React, { useState } from "react";

const colorVariants = [
  { id: 1, colorName: "Black", image: "https://via.placeholder.com/300x200?text=Black+Shoes" },
  { id: 2, colorName: "White", image: "https://via.placeholder.com/300x200?text=White+Shoes" },
  { id: 3, colorName: "Blue", image: "https://via.placeholder.com/300x200?text=Blue+Shoes" },
];

function ColorSwatch({ variant, isSelected, onSelect }) {
  return (
    <button onClick={() => onSelect(variant.id)} style={{ marginRight: "10px" }}>
      {variant.colorName}
    </button>
  );
}

export default function ProductColorPreview() {
  const [selectedColorId, setSelectedColorId] = useState(1);

  // TODO: derive selected variant
  const selectedVariant = colorVariants[0];

  return (
    <div style={{ padding: "20px" }}>
      <h2>Running Shoes</h2>

      <img src={selectedVariant.image} alt={selectedVariant.colorName} />

      <p>Selected Color: {selectedVariant.colorName}</p>

      <div style={{ marginTop: "10px" }}>
        {colorVariants.map((variant) => (
          <ColorSwatch
            key={variant.id}
            variant={variant}
            isSelected={false}
            onSelect={setSelectedColorId}
          />
        ))}
      </div>
    </div>
  );
}

Full Solution

import React, { useState } from "react";

const colorVariants = [
  { id: 1, colorName: "Black", image: "https://via.placeholder.com/300x200?text=Black+Shoes" },
  { id: 2, colorName: "White", image: "https://via.placeholder.com/300x200?text=White+Shoes" },
  { id: 3, colorName: "Blue", image: "https://via.placeholder.com/300x200?text=Blue+Shoes" },
];

function ColorSwatch({ variant, isSelected, onSelect }) {
  return (
    <button
      onClick={() => onSelect(variant.id)}
      style={{
        marginRight: "10px",
        padding: "8px 12px",
        backgroundColor: isSelected ? "#ddd" : "white",
      }}
    >
      {variant.colorName}
    </button>
  );
}

export default function ProductColorPreview() {
  const [selectedColorId, setSelectedColorId] = useState(1);

  const selectedVariant = colorVariants.find(
    (variant) => variant.id === selectedColorId
  );

  return (
    <div style={{ padding: "20px" }}>
      <h2>Running Shoes</h2>

      <img
        src={selectedVariant.image}
        alt={selectedVariant.colorName}
        style={{ width: "300px", height: "200px", objectFit: "cover" }}
      />

      <p>Selected Color: {selectedVariant.colorName}</p>

      <div style={{ marginTop: "10px" }}>
        {colorVariants.map((variant) => (
          <ColorSwatch
            key={variant.id}
            variant={variant}
            isSelected={selectedColorId === variant.id}
            onSelect={setSelectedColorId}
          />
        ))}
      </div>
    </div>
  );
}

Test Cases

  1. Action: App loads Expected: First color variant is selected by default.

  2. Action: User clicks White Expected: White image is shown.

  3. Action: User clicks Blue Expected: Blue image is shown.

  4. Action: User changes color Expected: Selected color name updates correctly.

  5. Action: Selected variant changes Expected: Active swatch is highlighted.

  6. Action: Previous selected color loses focus state Expected: Only one swatch appears selected.

  7. Action: User clicks different swatches repeatedly Expected: Preview image always matches the chosen swatch.

  8. Action: App first renders Expected: Product title, image, and selected color text are visible.

  9. Action: User clicks selected swatch again Expected: It remains selected without errors.

  10. Action: Image alt text is rendered Expected: It matches the selected color name.


Problem 5: Classroom Attendance Toggle List

Problem Statement

You are building a Classroom Attendance Toggle List for a teacher dashboard. The teacher sees a list of students and can mark whether each student is present or absent.

Each student row should have a button that toggles attendance status. The dashboard should also show a live summary of how many students are present and absent.

This is a practical feature for education tools, admin systems, and classroom management software. It gives students practice with updating objects inside arrays, deriving counts, props, and conditional rendering.

Functional Requirements

  1. Display a list of students.

  2. Each student should contain:

    • id
    • name
    • present
  3. Each student row should show:

    • student name
    • attendance status
    • a toggle button
  4. Clicking the button should switch:

    • Present → Absent
    • Absent → Present
  5. Present students should look visually different from absent students.

  6. Show summary counts:

    • total students
    • present students
    • absent students
  7. Use a child component for each student row.

Concepts Covered

  • State
  • Props
  • useState
  • List updates
  • Derived UI
  • Conditional styling

Hints

  1. Keep the students array in state.
  2. Use map() to update just the clicked student.
  3. Present and absent counts should be derived from the state array.
  4. Pass the toggle handler and student data through props.
  5. Styling can depend on the present boolean.

Code Stub

import React, { useState } from "react";

const initialStudents = [
  { id: 1, name: "Aarav", present: true },
  { id: 2, name: "Meera", present: false },
  { id: 3, name: "Kunal", present: true },
  { id: 4, name: "Ishita", present: false },
];

function StudentRow({ student, onToggle }) {
  return (
    <div style={{ border: "1px solid #ddd", padding: "10px", marginBottom: "10px" }}>
      <h4>{student.name}</h4>
      <p>Status: {student.present ? "Present" : "Absent"}</p>
      <button onClick={() => onToggle(student.id)}>
        {/* TODO */}
      </button>
    </div>
  );
}

export default function AttendanceToggleList() {
  const [students, setStudents] = useState(initialStudents);

  // TODO: implement toggle logic
  const handleToggleAttendance = (id) => {};

  const totalStudents = students.length;
  const presentCount = 0;
  const absentCount = 0;

  return (
    <div style={{ padding: "20px" }}>
      <h2>Attendance Dashboard</h2>

      <p>Total Students: {totalStudents}</p>
      <p>Present: {presentCount}</p>
      <p>Absent: {absentCount}</p>

      <div style={{ marginTop: "20px" }}>
        {students.map((student) => (
          <StudentRow
            key={student.id}
            student={student}
            onToggle={handleToggleAttendance}
          />
        ))}
      </div>
    </div>
  );
}

Full Solution

import React, { useState } from "react";

const initialStudents = [
  { id: 1, name: "Aarav", present: true },
  { id: 2, name: "Meera", present: false },
  { id: 3, name: "Kunal", present: true },
  { id: 4, name: "Ishita", present: false },
];

function StudentRow({ student, onToggle }) {
  return (
    <div
      style={{
        border: "1px solid #ddd",
        padding: "10px",
        marginBottom: "10px",
        backgroundColor: student.present ? "#eafbea" : "#fdeaea",
        borderRadius: "8px",
      }}
    >
      <h4>{student.name}</h4>
      <p>Status: {student.present ? "Present" : "Absent"}</p>
      <button onClick={() => onToggle(student.id)}>
        {student.present ? "Mark Absent" : "Mark Present"}
      </button>
    </div>
  );
}

export default function AttendanceToggleList() {
  const [students, setStudents] = useState(initialStudents);

  const handleToggleAttendance = (id) => {
    setStudents((prev) =>
      prev.map((student) =>
        student.id === id
          ? { ...student, present: !student.present }
          : student
      )
    );
  };

  const totalStudents = students.length;
  const presentCount = students.filter((student) => student.present).length;
  const absentCount = totalStudents - presentCount;

  return (
    <div style={{ padding: "20px" }}>
      <h2>Attendance Dashboard</h2>

      <p>Total Students: {totalStudents}</p>
      <p>Present: {presentCount}</p>
      <p>Absent: {absentCount}</p>

      <div style={{ marginTop: "20px" }}>
        {students.map((student) => (
          <StudentRow
            key={student.id}
            student={student}
            onToggle={handleToggleAttendance}
          />
        ))}
      </div>
    </div>
  );
}

Test Cases

  1. Action: App loads Expected: All students are displayed.

  2. Action: App loads Expected: Present and absent counts match the student data.

  3. Action: User clicks Mark Present on an absent student Expected: That student becomes present.

  4. Action: User clicks Mark Absent on a present student Expected: That student becomes absent.

  5. Action: Student attendance changes Expected: Present/absent summary updates correctly.

  6. Action: Present student row renders Expected: It uses present-specific styling.

  7. Action: Absent student row renders Expected: It uses absent-specific styling.

  8. Action: User toggles the same student twice Expected: Status switches back correctly.

  9. Action: User toggles multiple students Expected: All counts remain accurate.

  10. Action: Each row renders Expected: Student name, status, and correct button text are visible.


Great — here is Set 2 of the 30-question React practice sheet, covering Problems 6–10 in the same detailed format.

This set starts introducing slightly richer interaction patterns, including:

  • search-based filtering
  • parent-child coordination
  • lifting state up
  • basic useEffect
  • derived summaries
  • empty states
  • reusable components

Problem 6: Team Directory Search Panel

Problem Statement

You are building a Team Directory Search Panel for an internal company dashboard. Employees should be able to search through a list of team members by name and quickly find the person they are looking for.

This is a very common feature in admin tools, collaboration apps, HR portals, and internal dashboards. The goal is to help students practice controlled inputs, filtering lists, derived UI, and empty-state handling in React.

The interaction should feel realistic: the user types into the search bar, and the visible employee list updates immediately.

Functional Requirements

  1. Display a list of team members.

  2. Each team member should have:

    • id
    • name
    • role
  3. Add a search input at the top.

  4. The search input should be a controlled component.

  5. Filter members by name as the user types.

  6. Matching should be case-insensitive.

  7. Show the total number of visible results.

  8. If no team members match the search, show:

    • No team members found
  9. Use a child component to render each member card.

Concepts Covered

  • State
  • Props
  • useState
  • Controlled components
  • List filtering
  • Derived UI
  • Conditional rendering

Hints

  1. Keep the search value in state.
  2. Use filter() on the original members array.
  3. Convert both the member name and search text to lowercase before comparing.
  4. The visible count should come from the filtered array.
  5. The child component should only focus on display, not filtering logic.

Code Stub

import React, { useState } from "react";

const members = [
  { id: 1, name: "Aarav Sharma", role: "Frontend Engineer" },
  { id: 2, name: "Meera Nair", role: "Product Designer" },
  { id: 3, name: "Kabir Jain", role: "Backend Engineer" },
  { id: 4, name: "Ishita Rao", role: "QA Engineer" },
];

function MemberCard({ member }) {
  return (
    <div style={{ border: "1px solid #ddd", padding: "10px", marginBottom: "10px" }}>
      <h4>{member.name}</h4>
      <p>{member.role}</p>
    </div>
  );
}

export default function TeamDirectorySearch() {
  const [search, setSearch] = useState("");

  // TODO: derive visible members
  const visibleMembers = members;

  return (
    <div style={{ padding: "20px" }}>
      <h2>Team Directory</h2>

      <input
        type="text"
        placeholder="Search team members"
        value={search}
        onChange={(e) => setSearch(e.target.value)}
      />

      <p style={{ marginTop: "15px" }}>Visible Members: {visibleMembers.length}</p>

      <div style={{ marginTop: "15px" }}>
        {visibleMembers.map((member) => (
          <MemberCard key={member.id} member={member} />
        ))}
      </div>
    </div>
  );
}

Full Solution

import React, { useState } from "react";

const members = [
  { id: 1, name: "Aarav Sharma", role: "Frontend Engineer" },
  { id: 2, name: "Meera Nair", role: "Product Designer" },
  { id: 3, name: "Kabir Jain", role: "Backend Engineer" },
  { id: 4, name: "Ishita Rao", role: "QA Engineer" },
];

function MemberCard({ member }) {
  return (
    <div style={{ border: "1px solid #ddd", padding: "10px", marginBottom: "10px", borderRadius: "8px" }}>
      <h4>{member.name}</h4>
      <p>{member.role}</p>
    </div>
  );
}

export default function TeamDirectorySearch() {
  const [search, setSearch] = useState("");

  const visibleMembers = members.filter((member) =>
    member.name.toLowerCase().includes(search.toLowerCase())
  );

  return (
    <div style={{ padding: "20px" }}>
      <h2>Team Directory</h2>

      <input
        type="text"
        placeholder="Search team members"
        value={search}
        onChange={(e) => setSearch(e.target.value)}
      />

      <p style={{ marginTop: "15px" }}>Visible Members: {visibleMembers.length}</p>

      <div style={{ marginTop: "15px" }}>
        {visibleMembers.length === 0 ? (
          <p>No team members found</p>
        ) : (
          visibleMembers.map((member) => (
            <MemberCard key={member.id} member={member} />
          ))
        )}
      </div>
    </div>
  );
}

Test Cases

  1. Action: App loads Expected: All members are displayed.

  2. Action: User types aarav Expected: Only Aarav Sharma is visible.

  3. Action: User types MEERA Expected: Meera Nair is visible due to case-insensitive matching.

  4. Action: User types rao Expected: Ishita Rao is visible.

  5. Action: User clears the search input Expected: Full member list returns.

  6. Action: User types a name that does not exist Expected: No team members found is displayed.

  7. Action: Search text changes Expected: Visible member count updates immediately.

  8. Action: Search is empty Expected: Visible count matches the total member count.

  9. Action: Each member card renders Expected: Member name and role are visible.

  10. Action: User types partial text like ka Expected: Matching names containing ka are displayed.


Problem 7: Product Rating Filter Bar

Problem Statement

You are building a Product Rating Filter Bar for an ecommerce review page. Users should be able to filter products based on their star rating so they can quickly focus on the highest-rated or lower-rated items.

For example, a user may want to view only products with 4-star ratings or all products with 5 stars. This kind of filtering is common in marketplaces, food delivery platforms, shopping apps, and review sites.

This problem helps students practice state-driven filtering, buttons as filters, and list rendering with real product-style data.

Functional Requirements

  1. Display a list of products.

  2. Each product should have:

    • id
    • name
    • rating
  3. Add filter buttons:

    • All
    • 5 Stars
    • 4 Stars
    • 3 Stars
  4. By default, show all products.

  5. Clicking a filter button should show only products with that rating.

  6. Highlight the active filter button.

  7. Show the number of visible products.

  8. If no products match the filter, show:

    • No products available for this rating

Concepts Covered

  • State
  • useState
  • List filtering
  • Conditional rendering
  • Derived UI
  • Event handling

Hints

  1. Store the selected rating filter in state.
  2. All is just a special filter that returns the full array.
  3. Compare numeric ratings carefully.
  4. Use filtered array length to show the visible count.
  5. Button styles should depend on the active filter.

Code Stub

import React, { useState } from "react";

const products = [
  { id: 1, name: "Wireless Earbuds", rating: 5 },
  { id: 2, name: "Laptop Stand", rating: 4 },
  { id: 3, name: "Phone Holder", rating: 3 },
  { id: 4, name: "Bluetooth Speaker", rating: 5 },
];

export default function ProductRatingFilter() {
  const [activeFilter, setActiveFilter] = useState("All");

  // TODO: derive visible products
  const visibleProducts = products;

  return (
    <div style={{ padding: "20px" }}>
      <h2>Product Rating Filter</h2>

      <button onClick={() => setActiveFilter("All")}>All</button>
      <button onClick={() => setActiveFilter(5)} style={{ marginLeft: "10px" }}>
        5 Stars
      </button>
      <button onClick={() => setActiveFilter(4)} style={{ marginLeft: "10px" }}>
        4 Stars
      </button>
      <button onClick={() => setActiveFilter(3)} style={{ marginLeft: "10px" }}>
        3 Stars
      </button>

      <p style={{ marginTop: "15px" }}>Visible Products: {visibleProducts.length}</p>

      {visibleProducts.map((product) => (
        <div key={product.id} style={{ border: "1px solid #ddd", padding: "10px", marginBottom: "10px" }}>
          <h4>{product.name}</h4>
          <p>Rating: {product.rating} Stars</p>
        </div>
      ))}
    </div>
  );
}

Full Solution

import React, { useState } from "react";

const products = [
  { id: 1, name: "Wireless Earbuds", rating: 5 },
  { id: 2, name: "Laptop Stand", rating: 4 },
  { id: 3, name: "Phone Holder", rating: 3 },
  { id: 4, name: "Bluetooth Speaker", rating: 5 },
];

export default function ProductRatingFilter() {
  const [activeFilter, setActiveFilter] = useState("All");

  const visibleProducts =
    activeFilter === "All"
      ? products
      : products.filter((product) => product.rating === activeFilter);

  const getButtonStyle = (filterValue) => ({
    backgroundColor: activeFilter === filterValue ? "#ddd" : "white",
    padding: "8px 12px",
  });

  return (
    <div style={{ padding: "20px" }}>
      <h2>Product Rating Filter</h2>

      <button onClick={() => setActiveFilter("All")} style={getButtonStyle("All")}>
        All
      </button>
      <button onClick={() => setActiveFilter(5)} style={{ ...getButtonStyle(5), marginLeft: "10px" }}>
        5 Stars
      </button>
      <button onClick={() => setActiveFilter(4)} style={{ ...getButtonStyle(4), marginLeft: "10px" }}>
        4 Stars
      </button>
      <button onClick={() => setActiveFilter(3)} style={{ ...getButtonStyle(3), marginLeft: "10px" }}>
        3 Stars
      </button>

      <p style={{ marginTop: "15px" }}>Visible Products: {visibleProducts.length}</p>

      {visibleProducts.length === 0 ? (
        <p>No products available for this rating</p>
      ) : (
        visibleProducts.map((product) => (
          <div
            key={product.id}
            style={{ border: "1px solid #ddd", padding: "10px", marginBottom: "10px", borderRadius: "8px" }}
          >
            <h4>{product.name}</h4>
            <p>Rating: {product.rating} Stars</p>
          </div>
        ))
      )}
    </div>
  );
}

Test Cases

  1. Action: App loads Expected: All products are displayed.

  2. Action: User clicks 5 Stars Expected: Only 5-star products appear.

  3. Action: User clicks 4 Stars Expected: Only 4-star products appear.

  4. Action: User clicks 3 Stars Expected: Only 3-star products appear.

  5. Action: User clicks All Expected: All products appear again.

  6. Action: Active filter changes Expected: Correct button is highlighted.

  7. Action: No products exist for a selected rating Expected: No products available for this rating is shown.

  8. Action: Filter changes Expected: Visible product count updates correctly.

  9. Action: Product card renders Expected: Product name and rating are both visible.

  10. Action: User switches filters repeatedly Expected: UI remains accurate and stable.


Problem 8: Shared Selected Course Viewer

Problem Statement

You are building a Shared Selected Course Viewer for an education platform. On the left side, users should see a list of available courses. On the right side, they should see the details of the currently selected course.

The important part of this problem is that the selected course state should live in the parent component, while both the course list and the course details panel depend on it. This is a classic case of lifting state up.

This pattern is very common in dashboards, admin tools, media apps, CRMs, and learning platforms where multiple sibling components need to stay synchronized based on shared parent state.

Functional Requirements

  1. Show a list of courses in one child component.

  2. Show selected course details in another child component.

  3. Each course should include:

    • id
    • title
    • instructor
    • duration
  4. The first course should be selected by default.

  5. Clicking a course in the list should update the details panel.

  6. Highlight the selected course in the list.

  7. State for selected course should be stored in the parent component.

  8. The child components should communicate via props only.

Concepts Covered

  • State
  • Props
  • useState
  • Lifting state up
  • Parent-child communication
  • Dynamic rendering

Hints

  1. The parent should own selectedCourseId.
  2. Pass the selected ID and click handler down to the list component.
  3. Pass the selected course data down to the details component.
  4. Use find() in the parent to derive the selected course.
  5. This is a good example of siblings sharing one source of truth.

Code Stub

import React, { useState } from "react";

const courses = [
  { id: 1, title: "React Basics", instructor: "Mrinal", duration: "4 Weeks" },
  { id: 2, title: "Advanced JavaScript", instructor: "Aman", duration: "6 Weeks" },
  { id: 3, title: "Node.js Fundamentals", instructor: "Riya", duration: "5 Weeks" },
];

function CourseList({ courses, selectedCourseId, onSelect }) {
  return (
    <div>
      {courses.map((course) => (
        <button
          key={course.id}
          onClick={() => onSelect(course.id)}
          style={{ display: "block", marginBottom: "10px" }}
        >
          {course.title}
        </button>
      ))}
    </div>
  );
}

function CourseDetails({ course }) {
  return (
    <div>
      <h3>{course.title}</h3>
      <p>Instructor: {course.instructor}</p>
      <p>Duration: {course.duration}</p>
    </div>
  );
}

export default function SharedCourseViewer() {
  const [selectedCourseId, setSelectedCourseId] = useState(1);

  // TODO: derive selected course
  const selectedCourse = courses[0];

  return (
    <div style={{ padding: "20px" }}>
      <h2>Course Viewer</h2>

      <div style={{ display: "flex", gap: "20px" }}>
        <CourseList
          courses={courses}
          selectedCourseId={selectedCourseId}
          onSelect={setSelectedCourseId}
        />
        <CourseDetails course={selectedCourse} />
      </div>
    </div>
  );
}

Full Solution

import React, { useState } from "react";

const courses = [
  { id: 1, title: "React Basics", instructor: "Mrinal", duration: "4 Weeks" },
  { id: 2, title: "Advanced JavaScript", instructor: "Aman", duration: "6 Weeks" },
  { id: 3, title: "Node.js Fundamentals", instructor: "Riya", duration: "5 Weeks" },
];

function CourseList({ courses, selectedCourseId, onSelect }) {
  return (
    <div>
      {courses.map((course) => (
        <button
          key={course.id}
          onClick={() => onSelect(course.id)}
          style={{
            display: "block",
            marginBottom: "10px",
            backgroundColor: selectedCourseId === course.id ? "#ddd" : "white",
            padding: "8px 12px",
          }}
        >
          {course.title}
        </button>
      ))}
    </div>
  );
}

function CourseDetails({ course }) {
  return (
    <div style={{ border: "1px solid #ddd", padding: "15px", borderRadius: "8px", minWidth: "250px" }}>
      <h3>{course.title}</h3>
      <p>Instructor: {course.instructor}</p>
      <p>Duration: {course.duration}</p>
    </div>
  );
}

export default function SharedCourseViewer() {
  const [selectedCourseId, setSelectedCourseId] = useState(1);

  const selectedCourse = courses.find(
    (course) => course.id === selectedCourseId
  );

  return (
    <div style={{ padding: "20px" }}>
      <h2>Course Viewer</h2>

      <div style={{ display: "flex", gap: "20px" }}>
        <CourseList
          courses={courses}
          selectedCourseId={selectedCourseId}
          onSelect={setSelectedCourseId}
        />
        <CourseDetails course={selectedCourse} />
      </div>
    </div>
  );
}

Test Cases

  1. Action: App loads Expected: First course is selected by default.

  2. Action: User clicks second course Expected: Second course details appear on the right.

  3. Action: User clicks third course Expected: Third course details appear.

  4. Action: Selected course changes Expected: Correct course button is highlighted.

  5. Action: Previous selected course changes Expected: Previous course button loses highlight.

  6. Action: Course details panel renders Expected: Title, instructor, and duration are visible.

  7. Action: State changes in parent Expected: Both child components update consistently.

  8. Action: User switches courses repeatedly Expected: List and details panel remain synchronized.

  9. Action: Course list renders Expected: All course titles appear as selectable buttons.

  10. Action: App first loads Expected: List selection and details panel match each other.


Problem 9: Auto-Updating Welcome Banner

Problem Statement

You are building an Auto-Updating Welcome Banner for a learning dashboard. When the page loads, the banner should first show Loading user.... After 2 seconds, it should automatically update to show a welcome message such as Welcome back, Student!.

This simulates a simple asynchronous loading state that often appears in dashboards, profile screens, and SaaS apps when waiting for user data or remote content.

The purpose of this problem is to introduce students to useEffect for side effects and delayed UI updates.

Functional Requirements

  1. When the component first loads, show:

    • Loading user...
  2. After 2 seconds, automatically update the banner to:

    • Welcome back, Student!
  3. Use useEffect for the delayed update.

  4. Keep the logic in a single component.

  5. The UI should re-render automatically when the message changes.

Concepts Covered

  • State
  • useState
  • useEffect
  • Side effects
  • Conditional rendering

Hints

  1. Use one piece of state for the banner text.
  2. useEffect can run once after the component mounts.
  3. Use setTimeout() inside the effect.
  4. Update state after the delay.
  5. The initial state should represent loading.

Code Stub

import React, { useEffect, useState } from "react";

export default function WelcomeBanner() {
  const [message, setMessage] = useState("Loading user...");

  // TODO: update message after 2 seconds
  useEffect(() => {
  }, []);

  return (
    <div style={{ padding: "20px", border: "1px solid #ddd" }}>
      <h2>Dashboard Banner</h2>
      <p>{message}</p>
    </div>
  );
}

Full Solution

import React, { useEffect, useState } from "react";

export default function WelcomeBanner() {
  const [message, setMessage] = useState("Loading user...");

  useEffect(() => {
    const timerId = setTimeout(() => {
      setMessage("Welcome back, Student!");
    }, 2000);

    return () => clearTimeout(timerId);
  }, []);

  return (
    <div style={{ padding: "20px", border: "1px solid #ddd", borderRadius: "8px" }}>
      <h2>Dashboard Banner</h2>
      <p>{message}</p>
    </div>
  );
}

Test Cases

  1. Action: App loads Expected: Banner shows Loading user...

  2. Action: 2 seconds pass Expected: Banner updates to Welcome back, Student!

  3. Action: Before 2 seconds are complete Expected: Banner still shows loading message.

  4. Action: State updates after timeout Expected: Component re-renders with the new message.

  5. Action: Component unmounts before timeout completes Expected: Cleanup prevents unwanted timeout side effects.

  6. Action: App renders first time Expected: Heading and banner text are visible.

  7. Action: Delay completes only once Expected: Message changes once and stays stable.

  8. Action: App remains mounted Expected: Welcome message remains visible after update.

  9. Action: useEffect runs Expected: It runs only once on initial mount.

  10. Action: Timer finishes Expected: UI reflects updated message without manual refresh.


Problem 10: Recently Viewed Articles Tracker

Problem Statement

You are building a Recently Viewed Articles Tracker for a news platform. Users should be able to click article titles from a list and see the last article they viewed displayed in a small summary panel.

This is a realistic feature because news websites, blogs, learning platforms, and content apps often surface the user’s recently selected or last-viewed item. The key learning here is keeping one selected value in state and deriving the summary panel from it.

This problem is intentionally simple, but it builds strong foundational understanding for later “selected item” and “history” based UI patterns.

Functional Requirements

  1. Display a list of article titles.

  2. Each article should contain:

    • id
    • title
    • category
  3. By default, the summary panel should say:

    • No article viewed yet
  4. Clicking an article should update the summary panel with:

    • article title
    • article category
  5. The selected article row should be highlighted.

  6. Use a child component to render each article item.

Concepts Covered

  • State
  • Props
  • useState
  • Conditional rendering
  • Dynamic rendering
  • List rendering

Hints

  1. Store the last viewed article ID in state, starting with null.
  2. Use find() to locate the selected article.
  3. Only show the details panel when a valid article exists.
  4. Pass isSelected to the child component for highlighting.
  5. This is very similar to real content selection interfaces.

Code Stub

import React, { useState } from "react";

const articles = [
  { id: 1, title: "React 19 Features", category: "Frontend" },
  { id: 2, title: "Node.js Scaling Tips", category: "Backend" },
  { id: 3, title: "Designing Better APIs", category: "System Design" },
];

function ArticleItem({ article, isSelected, onView }) {
  return (
    <div
      onClick={() => onView(article.id)}
      style={{
        border: "1px solid #ddd",
        padding: "10px",
        marginBottom: "10px",
        cursor: "pointer",
      }}
    >
      <h4>{article.title}</h4>
      <p>{article.category}</p>
    </div>
  );
}

export default function ViewedArticlesTracker() {
  const [viewedArticleId, setViewedArticleId] = useState(null);

  // TODO: derive viewedArticle
  const viewedArticle = null;

  return (
    <div style={{ padding: "20px" }}>
      <h2>Articles</h2>

      <div>
        {articles.map((article) => (
          <ArticleItem
            key={article.id}
            article={article}
            isSelected={false}
            onView={setViewedArticleId}
          />
        ))}
      </div>

      <div style={{ marginTop: "20px" }}>
        <h3>Recently Viewed</h3>
        <p>No article viewed yet</p>
      </div>
    </div>
  );
}

Full Solution

import React, { useState } from "react";

const articles = [
  { id: 1, title: "React 19 Features", category: "Frontend" },
  { id: 2, title: "Node.js Scaling Tips", category: "Backend" },
  { id: 3, title: "Designing Better APIs", category: "System Design" },
];

function ArticleItem({ article, isSelected, onView }) {
  return (
    <div
      onClick={() => onView(article.id)}
      style={{
        border: "1px solid #ddd",
        padding: "10px",
        marginBottom: "10px",
        cursor: "pointer",
        backgroundColor: isSelected ? "#eef4ff" : "white",
        borderRadius: "8px",
      }}
    >
      <h4>{article.title}</h4>
      <p>{article.category}</p>
    </div>
  );
}

export default function ViewedArticlesTracker() {
  const [viewedArticleId, setViewedArticleId] = useState(null);

  const viewedArticle = articles.find(
    (article) => article.id === viewedArticleId
  );

  return (
    <div style={{ padding: "20px" }}>
      <h2>Articles</h2>

      <div>
        {articles.map((article) => (
          <ArticleItem
            key={article.id}
            article={article}
            isSelected={viewedArticleId === article.id}
            onView={setViewedArticleId}
          />
        ))}
      </div>

      <div style={{ marginTop: "20px", borderTop: "1px solid #ddd", paddingTop: "15px" }}>
        <h3>Recently Viewed</h3>
        {viewedArticle ? (
          <>
            <p>Title: {viewedArticle.title}</p>
            <p>Category: {viewedArticle.category}</p>
          </>
        ) : (
          <p>No article viewed yet</p>
        )}
      </div>
    </div>
  );
}

Test Cases

  1. Action: App loads Expected: Summary panel shows No article viewed yet.

  2. Action: User clicks first article Expected: Summary shows first article title and category.

  3. Action: User clicks second article Expected: Summary updates to second article details.

  4. Action: User clicks third article Expected: Summary updates to third article details.

  5. Action: An article is clicked Expected: That row becomes highlighted.

  6. Action: User clicks another article Expected: Previous highlight is removed and new one appears.

  7. Action: Before any click Expected: No row is highlighted.

  8. Action: Article list renders Expected: All titles and categories are shown.

  9. Action: User clicks the same article multiple times Expected: Summary remains correct and stable.

  10. Action: Selected article changes Expected: Summary panel and row highlight stay in sync.


Great — here is Set 3 of the 30-question React practice sheet, covering Problems 11–15 in the same detailed structure.

This set introduces a slightly wider range of React ideas, including:

  • toggling favorites/bookmarks
  • multiple filters working together
  • useRef for DOM interaction
  • useEffect with cleanup
  • slightly richer component composition

Problem 11: Saved Jobs Bookmark Board

Problem Statement

You are building a Saved Jobs Bookmark Board for a job search platform. Users can browse job cards and bookmark jobs they want to revisit later.

Each job card should show a bookmark button. When the user clicks it, the job should switch between saved and unsaved states. The dashboard should also display how many jobs are currently saved.

This is a realistic feature because almost every job board, ecommerce app, content platform, or marketplace allows users to save items for later review. This problem helps students practice updating objects inside arrays, passing handlers through props, and deriving summary counts.

Functional Requirements

  1. Display a list of job cards.

  2. Each job should include:

    • id
    • company
    • role
    • saved
  3. Each job card should show a button:

    • Save Job if not saved
    • Remove Saved if already saved
  4. Clicking the button should toggle the saved state for that job.

  5. Saved jobs should appear visually different.

  6. Show a summary count:

    • Saved Jobs: X
  7. Use a child component to render each job card.

Concepts Covered

  • State
  • Props
  • useState
  • List updates
  • Conditional rendering
  • Derived UI

Hints

  1. Keep the full jobs list in state.
  2. Use map() to update only the clicked job.
  3. The saved count can be derived using filter().
  4. Pass the job object and toggle handler into the child card component.
  5. Let the button label depend on the saved value.

Code Stub

import React, { useState } from "react";

const initialJobs = [
  { id: 1, company: "Google", role: "Frontend Engineer", saved: false },
  { id: 2, company: "Amazon", role: "SDE 1", saved: true },
  { id: 3, company: "Notion", role: "Product Engineer", saved: false },
];

function JobCard({ job, onToggleSaved }) {
  return (
    <div style={{ border: "1px solid #ddd", padding: "12px", marginBottom: "10px" }}>
      <h4>{job.company}</h4>
      <p>{job.role}</p>
      <button onClick={() => onToggleSaved(job.id)}>
        {/* TODO */}
      </button>
    </div>
  );
}

export default function SavedJobsBoard() {
  const [jobs, setJobs] = useState(initialJobs);

  // TODO: implement toggle logic
  const handleToggleSaved = (id) => {};

  const savedCount = 0;

  return (
    <div style={{ padding: "20px" }}>
      <h2>Saved Jobs</h2>
      <p>Saved Jobs: {savedCount}</p>

      {jobs.map((job) => (
        <JobCard key={job.id} job={job} onToggleSaved={handleToggleSaved} />
      ))}
    </div>
  );
}

Full Solution

import React, { useState } from "react";

const initialJobs = [
  { id: 1, company: "Google", role: "Frontend Engineer", saved: false },
  { id: 2, company: "Amazon", role: "SDE 1", saved: true },
  { id: 3, company: "Notion", role: "Product Engineer", saved: false },
];

function JobCard({ job, onToggleSaved }) {
  return (
    <div
      style={{
        border: "1px solid #ddd",
        padding: "12px",
        marginBottom: "10px",
        borderRadius: "8px",
        backgroundColor: job.saved ? "#fff7e6" : "white",
      }}
    >
      <h4>{job.company}</h4>
      <p>{job.role}</p>
      <button onClick={() => onToggleSaved(job.id)}>
        {job.saved ? "Remove Saved" : "Save Job"}
      </button>
    </div>
  );
}

export default function SavedJobsBoard() {
  const [jobs, setJobs] = useState(initialJobs);

  const handleToggleSaved = (id) => {
    setJobs((prev) =>
      prev.map((job) =>
        job.id === id ? { ...job, saved: !job.saved } : job
      )
    );
  };

  const savedCount = jobs.filter((job) => job.saved).length;

  return (
    <div style={{ padding: "20px" }}>
      <h2>Saved Jobs</h2>
      <p>Saved Jobs: {savedCount}</p>

      {jobs.map((job) => (
        <JobCard key={job.id} job={job} onToggleSaved={handleToggleSaved} />
      ))}
    </div>
  );
}

Test Cases

  1. Action: App loads Expected: All job cards are displayed.

  2. Action: App loads Expected: Saved job count matches the initial data.

  3. Action: User clicks Save Job on an unsaved job Expected: That job becomes saved.

  4. Action: User clicks Remove Saved on a saved job Expected: That job becomes unsaved.

  5. Action: A job becomes saved Expected: Its card styling changes to show saved state.

  6. Action: User toggles a job Expected: Saved count updates correctly.

  7. Action: User toggles the same job twice Expected: It returns to its original state.

  8. Action: Button label changes Expected: It reflects the new saved status.

  9. Action: User saves multiple jobs Expected: Saved count increases accordingly.

  10. Action: User unsaves all saved jobs Expected: Saved count becomes 0.


Problem 12: Product Catalog Search and Stock Filter

Problem Statement

You are building a Product Catalog Search and Stock Filter for an ecommerce admin panel. The admin should be able to search products by name and also toggle whether they want to see only in-stock products.

This problem introduces the idea of applying multiple filters together. The visible list should depend on both the search text and the stock-only toggle.

This is a very realistic interaction pattern because most dashboards, admin tools, and catalog screens allow users to combine search and filters at the same time.

Functional Requirements

  1. Display a list of products.

  2. Each product should include:

    • id
    • name
    • category
    • inStock
  3. Add a search input for product name.

  4. The search input should be a controlled component.

  5. Add a checkbox:

    • Show in-stock only
  6. The visible product list should update based on:

    • current search text
    • checkbox status
  7. Matching search should be case-insensitive.

  8. Show visible product count.

  9. If no products match, show:

    • No matching products found

Concepts Covered

  • State
  • useState
  • Controlled components
  • Multiple filters
  • Derived UI
  • Conditional rendering

Hints

  1. Keep search text and stock-only state separate.
  2. Apply both conditions when deriving the visible list.
  3. You can first filter by search and then by stock, or do both in one filter callback.
  4. Use checked for checkbox state.
  5. The visible count should come from the filtered list.

Code Stub

import React, { useState } from "react";

const products = [
  { id: 1, name: "Wireless Mouse", category: "Accessories", inStock: true },
  { id: 2, name: "Mechanical Keyboard", category: "Accessories", inStock: false },
  { id: 3, name: "Laptop Stand", category: "Office", inStock: true },
  { id: 4, name: "USB-C Hub", category: "Electronics", inStock: true },
];

export default function ProductSearchStockFilter() {
  const [search, setSearch] = useState("");
  const [showInStockOnly, setShowInStockOnly] = useState(false);

  // TODO: derive visible products using both filters
  const visibleProducts = products;

  return (
    <div style={{ padding: "20px" }}>
      <h2>Product Catalog</h2>

      <input
        type="text"
        placeholder="Search products"
        value={search}
        onChange={(e) => setSearch(e.target.value)}
      />

      <label style={{ display: "block", marginTop: "10px" }}>
        <input
          type="checkbox"
          checked={showInStockOnly}
          onChange={(e) => setShowInStockOnly(e.target.checked)}
        />
        Show in-stock only
      </label>

      <p style={{ marginTop: "15px" }}>Visible Products: {visibleProducts.length}</p>

      {visibleProducts.map((product) => (
        <div key={product.id} style={{ border: "1px solid #ddd", padding: "10px", marginBottom: "10px" }}>
          <h4>{product.name}</h4>
          <p>{product.category}</p>
          <p>{product.inStock ? "In Stock" : "Out of Stock"}</p>
        </div>
      ))}
    </div>
  );
}

Full Solution

import React, { useState } from "react";

const products = [
  { id: 1, name: "Wireless Mouse", category: "Accessories", inStock: true },
  { id: 2, name: "Mechanical Keyboard", category: "Accessories", inStock: false },
  { id: 3, name: "Laptop Stand", category: "Office", inStock: true },
  { id: 4, name: "USB-C Hub", category: "Electronics", inStock: true },
];

export default function ProductSearchStockFilter() {
  const [search, setSearch] = useState("");
  const [showInStockOnly, setShowInStockOnly] = useState(false);

  const visibleProducts = products.filter((product) => {
    const matchesSearch = product.name
      .toLowerCase()
      .includes(search.toLowerCase());

    const matchesStock = showInStockOnly ? product.inStock : true;

    return matchesSearch && matchesStock;
  });

  return (
    <div style={{ padding: "20px" }}>
      <h2>Product Catalog</h2>

      <input
        type="text"
        placeholder="Search products"
        value={search}
        onChange={(e) => setSearch(e.target.value)}
      />

      <label style={{ display: "block", marginTop: "10px" }}>
        <input
          type="checkbox"
          checked={showInStockOnly}
          onChange={(e) => setShowInStockOnly(e.target.checked)}
        />
        Show in-stock only
      </label>

      <p style={{ marginTop: "15px" }}>Visible Products: {visibleProducts.length}</p>

      {visibleProducts.length === 0 ? (
        <p>No matching products found</p>
      ) : (
        visibleProducts.map((product) => (
          <div
            key={product.id}
            style={{ border: "1px solid #ddd", padding: "10px", marginBottom: "10px", borderRadius: "8px" }}
          >
            <h4>{product.name}</h4>
            <p>{product.category}</p>
            <p>{product.inStock ? "In Stock" : "Out of Stock"}</p>
          </div>
        ))
      )}
    </div>
  );
}

Test Cases

  1. Action: Ap