πŸŸ₯ - Hard Flashcards

Hard level React coding assignments (11 cards)

1
Q

Product Store

Description
Build a simple Product Store using react-router-dom.
The app should include the following:
Folder Structure
* App. js: Main routing and navigation
* Home. j sx: Home page component
* Products. jsx: Product listing page that fetches products
* ProductDetails. jsx: Displays detailed info about a selected product
* styles. css: Contains all styling

Functionality Requirements
1. Routing Setup
* Use BrowserRouter, Routes, and Route from react-router-dom.
* Create routes:
/ β†’ Home Page
/products β†’ Product List Page
/products/:productId β†’ Product Details Page

  1. Navigation Bar
    * Display a navigation bar with Home and Products links.
  2. Home Page
    * Display a simple welcome message: “Welcome to the Home Page”
  3. Product List Page
    * Display a text “Product List”
    * Fetch product data from:
    https://dummyjson.com/products
    * Display each product in a card layout showing:
    Product image
    Title
    Short description
    A “View More” link that navigates to product details
    The “View More” link must have an id =
    “product-${productld}”
    Show Loading… during fetch and display error messages if the API fails.
  4. Product Details Page
    * Fetch product details from :
    https://dummyjson.com/products/${productId}
    * Display:
    Title
    Image
    Full description
    Price
    A “Back to Products” link

https://namastedev.com/practice/product-store

A

styles.css

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

/* Navbar Styles */
.navbar {
  background-color: skyblue;
  padding: 10px;
  display: flex;
  justify-content: end;
}

.navLink {
  color: white;
  margin: 0 15px;
  text-decoration: none;
  font-size: 15px;
}
.navLink:hover {
  color: blue;
}

/* Home Page Styling */
.home {
  width: 100vw;
  height: 100vh;
  margin-top: 50px;
  text-align: center;
}

/* Products Page Styles */
.products {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}
.products h2 {
  margin-block: 20px;
}
.product-list{
   display: flex;
   flex-wrap: wrap;
  align-items: center;
}

.product-card {
  background-color: #fff;
  padding: 10px;
  margin: 10px;
  border-radius: 8px;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
  transition: transform 0.3s ease;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  height: 280px;
  width: 200px;
}

.product-card:hover {
  transform: translateY(-5px);
}

.product-image {
  max-height: 80px;
  max-width: 80px;
}

.product-info {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  grid-gap: 10px;
  margin-top: 15px;
}

.product-card p {
  font-size: small;
  color: #7f8c8d;
  margin-bottom: 10px;
  padding-block: 10px;
}

.view-more {
  color: #3498db;
  text-decoration: none;
  font-size: 1.1em;
}

.view-more:hover {
  text-decoration: underline;
}

/* Product Details Page Styles */
.product-details {
  max-width: 350px;
  margin: 20px auto;
  padding: 20px;
  background-color: #fff;
  border-radius: 8px;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}

.product-details p {
  color: #555;
}

.back-to-products {
  color: #3498db;
  text-decoration: none;
}

App.js

import React from "react";
import { Routes, Route, Link } from "react-router-dom";
import Home from "./Home";
import Products from "./Products";
import ProductDetails from "./ProductDetails";
import './styles.css';

const App = () => (
  <>
    <nav className="navbar">
      <Link to="/" className="navLink">Home</Link>
      <Link to="/products" className="navLink">Products</Link>
    </nav>
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/products" element={<Products />} />
      <Route path="/products/:productId" element={<ProductDetails />} />
    </Routes>
  </>
);

export default App;

Home.jsx

import React from "react";

const Home = () => {
  return (
    <div className="home">
      <h1>Welcome to the Home Page</h1>
    </div>
  );
};

export default Home;

Products.jsx

import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";

const Products = () => {
  const [products, setProducts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState("");

  useEffect(() => {
    fetch("https://dummyjson.com/products")
      .then(res => {
        if (!res.ok) throw new Error("Failed to fetch products");
        return res.json();
      })
      .then(data => {
        setProducts(data.products);
        setLoading(false);
      })
      .catch(err => {
        setError("Error fetching products");
        setLoading(false);
      });
  }, []);

  return (
    <div className="products">
      <h2>Product List</h2>
      {loading && <p>Loading...</p>}
      {error && <p style={{ color: 'red' }}>{error}</p>}
      <div className="product-list">
        {products.map(product => (
          <div key={product.id} className="product-card">
            <img
              src={product.thumbnail}
              alt={product.title}
              className="product-image"
            />
            <div className="product-info">
              <h4>{product.title}</h4>
              <p>{product.description.slice(0, 50)}...</p>
              <Link
                id={`product-${product.id}`}
                to={`/products/${product.id}`}
                className="view-more"
              >
                View More
              </Link>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
};

export default Products;

ProductDetails.jsx

import React, { useState, useEffect } from "react";
import { useParams, Link } from "react-router-dom";

const ProductDetails = () => {
  const { productId } = useParams();
  const [product, setProduct] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState("");

  useEffect(() => {
    fetch(`https://dummyjson.com/products/${productId}`)
      .then(res => {
        if (!res.ok) throw new Error("Product not found");
        return res.json();
      })
      .then(data => {
        setProduct(data);
        setLoading(false);
      })
      .catch(err => {
        setError("Error loading product details");
        setLoading(false);
      });
  }, [productId]);

  if (loading) return <p className="product-details">Loading...</p>;
  if (error) return <p className="product-details" style={{ color: "red" }}>{error}</p>;

  return (
    <div className="product-details">
      <h3>{product.title}</h3>
      <img src={product.thumbnail} alt={product.title} style={{ maxWidth: '100%', height: 'auto', margin: '15px 0' }} />
      <p>{product.description}</p>
      <p><strong>Price:</strong> ${product.price}</p>
      <Link to="/products" className="back-to-products">Back to Products</Link>
    </div>
  );
};

export default ProductDetails;
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

Pagination

**Description **
Build a React component called Pagination that displays a list of products fetched from a remote API. The products should be displayed in a paginated format, with 10 items per page and navigational buttons to go to the previous and next pages.

Requirements:
1. Fetch products from this API endpoint:
https://dummyjson.com/products?limit=200
2. Display 10 products per page with:
Thumbnail image
* Title
3. Include pagination controls:
A Previous button with id=”previous” to navigate to the previous page. It should be disabled on the first page.
A Next button with id=”next” to navigate to the next page. It should be disabled on the last page.
Buttons for each page number (1-20) (highlight the active one)
4. Clicking “Next” should go to the next page.
Clicking “Previous” should go to the previous page:
Clicking a page number (1-20) should directly load that page.
5. Show “No products found” mesage if the product list is empty.

Additional Notes:
* You must use useState and useEffect.
* Do not use any third-party pagination libraries.
* Use functional components only.
* You may use react-icons, FiChevronsLeft for left arrow icon and FiChevronsLeft for right arrow icon (optional).

https://namastedev.com/practice/pagination

A

styles.css

.pagination-container {
  padding: 20px;
  text-align: center;
  font-family: Arial, sans-serif;
}

.product-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 20px;
  margin-block: 20px;
}

.product-card {
  border: 1px solid #ccc;
  border-radius: 8px;
  padding: 10px;
  width: 150px;
  background-color: #f9f9f9;
}

.product-image {
  max-width: 100%;
  height: 100px;
  object-fit: cover;
  border-radius: 4px;
}

.pagination-controls {
  margin-top: 20px;
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 8px;
}

button {
  padding: 6px 12px;
  font-size: 14px;
  cursor: pointer;
  border: none;
  border-radius: 4px;
  background: #3498db;
  color: white;
}

button:disabled {
  background-color: #ccc;
  cursor: not-allowed;
}

.page-btn.active {
  background-color: #2ecc71;
}

.no-products, .error {
  color: red;
  font-weight: bold;
}

App.js

import Pagination from './Pagination.js'
export default function App() {
  return <Pagination/>
}

Pagination.js

import { useState, useEffect } from "react";
import './styles.css';
import ProductCard from './ProductCard';
import { FiChevronsLeft, FiChevronsRight } from "react-icons/fi";

const PAGE_SIZE = 10;

const Pagination = () => {
  const [products, setProducts] = useState([]);
  const [currentPage, setCurrentPage] = useState(1);
  const [error, setError] = useState("");

  useEffect(() => {
    fetch("https://dummyjson.com/products?limit=200")
      .then(res => {
        if (!res.ok) throw new Error("Failed to fetch products");
        return res.json();
      })
      .then(data => setProducts(data.products))
      .catch(err => setError("No products found"));
  }, []);

  const totalPages = Math.ceil(products.length / PAGE_SIZE);
  const startIdx = (currentPage - 1) * PAGE_SIZE;
  const currentProducts = products.slice(startIdx, startIdx + PAGE_SIZE);

  const goToPage = (page) => {
    setCurrentPage(page);
  };

  return (
    <div className="pagination-container">
      <h1>Pagination</h1>

      {error ? (
        <p className="error">{error}</p>
      ) : currentProducts.length === 0 ? (
        <p className="no-products">No products found</p>
      ) : (
        <>
          <div className="product-grid">
            {currentProducts.map(product => (
              <ProductCard
                key={product.id}
                image={product.thumbnail}
                title={product.title}
              />
            ))}
          </div>

          <div className="pagination-controls">
            <button
              id="previous"
              onClick={() => goToPage(currentPage - 1)}
              disabled={currentPage === 1}
            >
              <FiChevronsLeft /> Previous
            </button>

            {[...Array(totalPages)].map((_, i) => (
              <button
                key={i + 1}
                className={`page-btn ${currentPage === i + 1 ? 'active' : ''}`}
                onClick={() => goToPage(i + 1)}
              >
                {i + 1}
              </button>
            ))}

            <button
              id="next"
              onClick={() => goToPage(currentPage + 1)}
              disabled={currentPage === totalPages}
            >
              Next <FiChevronsRight />
            </button>
          </div>
        </>
      )}
    </div>
  );
};

export default Pagination;

ProductCard.js

const ProductCard = ({ image, title }) => {
  return (
    <div className="product-card">
      <img src={image} alt={title} className="product-image" />
      <h4>{title}</h4>
    </div>
  );
};

export default ProductCard;
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

Nested Checkbox

Description
You have to implement a nested checkbox component that handles a tree-like structure of checkboxes. The component needs to support the following behavior:
Functional Requirements:
1. Parent β†’ Child Behavior:
When a parent checkbox is checked or unchecked, all its children and nested children should follow the same state.
2. Child - Parent Behavior:
When all children of a parent are checked, the parent should automatically become checked.
If any child is unchecked, the parent should become
unchecked.
3. Recursive Tree Structure:
The checkbox tree can have multiple levels of nesting

Important Note
You must use the same array structure provided below (CheckboxesData) because all the test cases are written based on this specific tree:

const CheckboxesData = [
  {
    id: 1,
    label: "Fruits",
    children: [
      { id: 2, label: "Apple" },
      { id: 3, label: "Banana" },
      {
        id: 4,
        label: "Citrus",
        children: [
          { id: 5, label: "Orange" },
          { id: 6, label: "Lemon" },
        ],
      },
    ],
  },
  {
    id: 7,
    label: "Vegetables",
    children: [
      { id: 8, label: "Carrot" },
      { id: 9, label: "Broccoli" },
    ],
  },
];

https://namastedev.com/practice/nested-checkbox

A

styles.css

body {
  font-family: sans-serif;
  -webkit-font-smoothing: auto;
  -moz-font-smoothing: auto;
  -moz-osx-font-smoothing: grayscale;
  font-smoothing: auto;
  text-rendering: optimizeLegibility;
  font-smooth: always;
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;
}

h1 {
  font-size: 1.5rem;
}

NestedCheckbox.js

import { useState } from "react";
import './styles.css';

const CheckboxesData = [
  {
    id: 1,
    label: "Fruits",
    children: [
      { id: 2, label: "Apple" },
      { id: 3, label: "Banana" },
      {
        id: 4,
        label: "Citrus",
        children: [
          { id: 5, label: "Orange" },
          { id: 6, label: "Lemon" },
        ],
      },
    ],
  },
  {
    id: 7,
    label: "Vegetables",
    children: [
      { id: 8, label: "Carrot" },
      { id: 9, label: "Broccoli" },
    ],
  },
];

// Helper: Get all IDs in a subtree
const getAllIds = (node) => {
  let ids = [node.id];
  if (node.children) {
    for (const child of node.children) {
      ids = ids.concat(getAllIds(child));
    }
  }
  return ids;
};

// Recursive Checkboxes
const Checkboxes = ({ data, checkedMap, setCheckedMap, parentMap }) => {
  const handleChange = (id, node, isChecked) => {
    const updated = { ...checkedMap };
    const allIds = getAllIds(node);
    allIds.forEach((nid) => (updated[nid] = isChecked));

    // Update parents
    let parentId = parentMap[id];
    while (parentId) {
      const siblings = getSiblings(parentId, parentMap);
      const allSiblingsChecked = siblings.every((siblingId) => updated[siblingId]);
      updated[parentId] = allSiblingsChecked;
      parentId = parentMap[parentId];
    }

    setCheckedMap(updated);
  };

  // Helper: Get all direct children of the same parent
  const getSiblings = (childId, parentMap) => {
    const parentId = parentMap[childId];
    return Object.keys(parentMap)
      .filter((key) => parentMap[key] === parentId)
      .map(Number);
  };

  return (
    <ul style={{ listStyle: "none" }}>
      {data.map((node) => (
        <li key={node.id}>
          <label>
            <input
              type="checkbox"
              checked={!!checkedMap[node.id]}
              onChange={(e) => handleChange(node.id, node, e.target.checked)}
            />
            {node.label}
          </label>
          {node.children && (
            <Checkboxes
              data={node.children}
              checkedMap={checkedMap}
              setCheckedMap={setCheckedMap}
              parentMap={parentMap}
            />
          )}
        </li>
      ))}
    </ul>
  );
};

// Build parent-child relationships
const buildParentMap = (nodes, parentId = null, map = {}) => {
  for (const node of nodes) {
    if (parentId !== null) {
      map[node.id] = parentId;
    }
    if (node.children) {
      buildParentMap(node.children, node.id, map);
    }
  }
  return map;
};

export default function NestedCheckbox() {
  const [checkedMap, setCheckedMap] = useState({});
  const parentMap = buildParentMap(CheckboxesData);

  return (
    <div>
      <h2>Nested Checkbox</h2>
      <Checkboxes
        data={CheckboxesData}
        checkedMap={checkedMap}
        setCheckedMap={setCheckedMap}
        parentMap={parentMap}
      />
    </div>
  );
}

App.js

import NestedCheckbox from './NestedCheckbox'
export default function App() {
  return <NestedCheckbox/>
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

File Explorer

Description
This project implements a dynamic and recursive file explorer component in React. It mimics the behavior of a typical file system viewer, where folders can be expanded or collapsed, and users can add or remove files and folders at any level of the hierarchy.

Key Features:
* Recursive Rendering: Folders can contain nested folders/files to any depth, handled with recursion.
* Expand/Collapse Ul: Each folder has toggle icons (using react-icons) to show/hide its contents.
* Add Items: Users can add a file or folder inside any existing folder dynamically.
* Remove Items: Files or folders (and their children) can be removed instantly.
* State Management: The file tree is managed using React useState, and state updates propagate through the recursive structure.
* Minimal Styling: Clean, readable layout using inline styles and icons for better UX.
* Design a modal that appears when users click “Add Folder” or “Add File”. The modal should contain:
An input field (<input></input> where users can type the name of a new file or folder.
An Add button (<button>) to submit the name.
A Cancel button (<button>) to close the modal without making any changes.</button></button>

Icons Used
* MdExpandMore - Folder collapsed
β†’ import { MdExpandMore } from “react-icons/md”
* MdExpandLess - Folder expanded
β†’ import { MdExpandLess } from “react-icons/md”
* MdDeleteOutline - Delete file/folder
β†’ import { MdDeleteOutline } from “react-icons/md”
* FiFolderPlus - Add new folder
β†’ import { FiFolderPlus } from “react-icons/fi”;
* AiOutlineFileAdd - Add new file
β†’ import & AiOutlineFileAdd } from “react-icons/ai”;

You need to ensure that data-testid attributes are added to the relevant elements .
* Add data-testid=”add” to the “Add” button inside the modal.
* Add data-testid=”cancel” to the “Cancel” button inside the modal.
* For the “Add Folder” icon, add data-testid=”add-folder-{id}”, where {id} is the ID of the folder.
* For the “Add File” icon, add data-testid=”add-file-fid}”, where {id} is the ID of the file.
* For the “Delete” icon, add data-testid=”delete” to each delete button.

https://namastedev.com/practice/file-explorer

A

styles.css

body {
  font-family: sans-serif;
  -webkit-font-smoothing: auto;
  -moz-font-smoothing: auto;
  -moz-osx-font-smoothing: grayscale;
  font-smoothing: auto;
  text-rendering: optimizeLegibility;
  font-smooth: always;
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;
}

h1 {
  font-size: 1.5rem;
}

App.js

import  FileExplorer from './FileExplorer.js'
export default function App() {
  return <FileExplorer/>
}

FileAndfolder.js

import { MdExpandLess, MdExpandMore, MdDeleteOutline } from "react-icons/md";
import { FiFolderPlus } from "react-icons/fi";
import { AiOutlineFileAdd } from "react-icons/ai";
import { useState } from "react";

const FileAndFolder = ({ data, addItem, deleteItem }) => {
  return (
    <div style={{ paddingLeft: "20px" }}>
      {data.map((item) => (
        <Node
          key={item.id}
          node={item}
          addItem={addItem}
          deleteItem={deleteItem}
        />
      ))}
    </div>
  );
};

const Node = ({ node, addItem, deleteItem }) => {
  const [isOpen, setIsOpen] = useState(false);
  const [showModal, setShowModal] = useState(false);
  const [newName, setNewName] = useState("");
  const [isNewFolder, setIsNewFolder] = useState(true);

  const toggle = () => setIsOpen((prev) => !prev);

  const handleAdd = () => {
    if (newName.trim()) {
      addItem(node.id, newName, isNewFolder);
      setNewName("");
      setShowModal(false);
    }
  };

  return (
    <div style={{ marginTop: "8px" }}>
      <div style={{ display: "flex", alignItems: "center", gap: "5px" }}>
        {node.isFolder ? (
          <>
            <span onClick={toggle} style={{ cursor: "pointer" }}>
              {isOpen ? <MdExpandLess /> : <MdExpandMore />}
            </span>
            <strong>{node.name}</strong>
            <FiFolderPlus
              data-testid={`add-folder-${node.id}`}
              onClick={() => {
                setIsNewFolder(true);
                setShowModal(true);
              }}
              style={{ cursor: "pointer" }}
            />
            <AiOutlineFileAdd
              data-testid={`add-file-${node.id}`}
              onClick={() => {
                setIsNewFolder(false);
                setShowModal(true);
              }}
              style={{ cursor: "pointer" }}
            />
            <MdDeleteOutline
              data-testid="delete"
              onClick={() => deleteItem(node.id)}
              style={{ cursor: "pointer", color: "red" }}
            />
          </>
        ) : (
          <>
            <span style={{ marginLeft: "20px" }}>{node.name}</span>
            <MdDeleteOutline
              data-testid="delete"
              onClick={() => deleteItem(node.id)}
              style={{ cursor: "pointer", color: "red" }}
            />
          </>
        )}
      </div>

      {node.isFolder && isOpen && node.children && (
        <FileAndFolder
          data={node.children}
          addItem={addItem}
          deleteItem={deleteItem}
        />
      )}

      {showModal && (
        <div
          style={{
            marginTop: "10px",
            padding: "10px",
            border: "1px solid #ccc",
            width: "250px",
            background: "#f9f9f9",
          }}
        >
          <input
            value={newName}
            onChange={(e) => setNewName(e.target.value)}
            placeholder={`Enter ${isNewFolder ? "folder" : "file"} name`}
          />
          <div style={{ marginTop: "10px" }}>
            <button data-testid="add" onClick={handleAdd}>
              Add
            </button>
            <button
              data-testid="cancel"
              onClick={() => setShowModal(false)}
              style={{ marginLeft: "10px" }}
            >
              Cancel
            </button>
          </div>
        </div>
      )}
    </div>
  );
};

export default FileAndFolder;

FileExplorer.js

import { useState } from "react";
import FileAndFolder from "./FileAndFolder";
import "./styles.css";

const initialData = [
  {
    id: 1,
    name: "public",
    isFolder: true,
    children: [{ id: 2, name: "index.html", isFolder: false }],
  },
  {
    id: 3,
    name: "src",
    isFolder: true,
    children: [
      { id: 4, name: "App.js", isFolder: false },
      { id: 5, name: "index.js", isFolder: false },
    ],
  },
  { id: 6, name: "package.json", isFolder: false },
];

export default function FileExplorer() {
  const [data, setData] = useState(initialData);
  const [idCounter, setIdCounter] = useState(7);

  const addItem = (parentId, name, isFolder) => {
    const newItem = {
      id: idCounter,
      name,
      isFolder,
      ...(isFolder ? { children: [] } : {}),
    };
    setData(updateTree(data, parentId, newItem));
    setIdCounter(idCounter + 1);
  };

  const deleteItem = (id) => {
    setData(removeNode(data, id));
  };

  return (
    <div>
      <h2>File Explorer</h2>
      <FileAndFolder data={data} addItem={addItem} deleteItem={deleteItem} />
    </div>
  );
}

// Recursively add item
function updateTree(tree, targetId, newItem) {
  return tree.map(node => {
    if (node.id === targetId && node.isFolder) {
      return {
        ...node,
        children: [...node.children, newItem],
      };
    }
    if (node.children) {
      return { ...node, children: updateTree(node.children, targetId, newItem) };
    }
    return node;
  });
}

// Recursively remove item
function removeNode(tree, targetId) {
  return tree
    .filter(node => node.id !== targetId)
    .map(node => {
      if (node.children) {
        return { ...node, children: removeNode(node.children, targetId) };
      }
      return node;
    });
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

Data Table

Description
Build a React component that renders a table from given data with pagination and page size selection. Users should be able to:
* View tabular data.
* Navigate between pages.
* Change how many rows are shown per page.

Requirements
1. Accept a list of objects as a prop called data.
* data: An array of objects, where each object has id, name, and age.
2. Table Structure
Display headers: “ΒΏd”, “name”, and “age”.
Render data rows based on the current page and selected page size.
3. Pagination
Show a “Next” and “Previous” button.
Initially display 5 rows per page.
Clicking “Next” should move to the next page of data.
Clicking “Previous” should go back one page.
The “Previous” button should be disabled on the first page.
4. Page Size Selector
The default page size must be 5.
Provide a dropdown to change page size (e.g., 5, 10, 20).
Selecting a new size should update the number of rows shown accordingly.

Constraints & Edge Cases
* Changing page size should reset the current page to 1.
* Do not allow navigating beyond available pages.

https://namastedev.com/practice/data-table

A

styles.css

body {
  font-family: sans-serif;
  -webkit-font-smoothing: auto;
  -moz-font-smoothing: auto;
  -moz-osx-font-smoothing: grayscale;
  font-smoothing: auto;
  text-rendering: optimizeLegibility;
  font-smooth: always;
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;
}

h1 {
  font-size: 1.5rem;
}

DataTable.js

import React, { useState } from "react";
import "./styles.css";

function DataTable({ data }) {
  const [pageSize, setPageSize] = useState(5);
  const [currentPage, setCurrentPage] = useState(1);

  const totalPages = Math.ceil(data.length / pageSize);
  const startIdx = (currentPage - 1) * pageSize;
  const endIdx = startIdx + pageSize;
  const currentData = data.slice(startIdx, endIdx);

  const handlePrevious = () => {
    if (currentPage > 1) setCurrentPage(currentPage - 1);
  };

  const handleNext = () => {
    if (currentPage < totalPages) setCurrentPage(currentPage + 1);
  };

  const handlePageSizeChange = (e) => {
    setPageSize(Number(e.target.value));
    setCurrentPage(1); // Reset to first page on page size change
  };

  return (
    <div>
      <h1>Data Table</h1>
      <table border="1" cellPadding="10" style={{ borderCollapse: "collapse", width: "100%" }}>
        <thead>
          <tr>
            <th>id</th>
            <th>name</th>
            <th>age</th>
          </tr>
        </thead>
        <tbody>
          {currentData.map((row) => (
            <tr key={row.id}>
              <td>{row.id}</td>
              <td>{row.name}</td>
              <td>{row.age}</td>
            </tr>
          ))}
        </tbody>
      </table>

      <div style={{ display: "flex", justifyContent: "space-between", marginTop: "10px" }}>
        <div style={{ display: "inline", margin: "0 10px" }}>
          <button onClick={handlePrevious} disabled={currentPage === 1}>
            Previous
          </button>
          <span style={{ margin: "0 10px" }}>
            Page {currentPage} of {totalPages}
          </span>
          <button onClick={handleNext} disabled={currentPage === totalPages}>
            Next
          </button>
        </div>
        <div style={{ display: "inline", margin: "0 10px" }}>
          <label htmlFor="pageSize">Rows per page: </label>
          <select id="pageSize" value={pageSize} onChange={handlePageSizeChange}>
            <option value={5}>5</option>
            <option value={10}>10</option>
            <option value={20}>20</option>
          </select>
        </div>
      </div>
    </div>
  );
}

export default DataTable;

App.js

import DataTable from "./DataTable";

export default function App() {
    const sampleData = [
        { id: 1, name: "Alice", age: 25 },
        { id: 2, name: "Bob", age: 30 },
        { id: 3, name: "Charlie", age: 22 },
        { id: 4, name: "David", age: 28 },
        { id: 5, name: "Eve", age: 27 },
        { id: 6, name: "Frank", age: 33 },
        { id: 7, name: "Grace", age: 24 },
        { id: 8, name: "Hank", age: 26 },
        { id: 9, name: "Ivy", age: 21 },
        { id: 10, name: "Jack", age: 29 }
    ];

    return <DataTable data={sampleData} />;
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

OTP Input Component

Description
Create a 4-digit OTP (One-Time Password), input component using React. The OTP should be entered one digit per input box. The focus should auto-move as the user types or deletes, and the component should support pasting a full OTP.
It should also reject any non-numeric characters.
Constraints & Edge Cases
* Only numeric input is allowed.
* If a box is empty and backspace is pressed, move focus to the previous box.
* Paste (e.g. “1234”) should fill all 4 boxes correctly.

Requirements
* Create a component that renders 4 input boxes, each accepting only one numeric digit.
* Automatically move focus to the next input when a digit is entered.
* Automatically move focus to the previous input when backspace is pressed on an empty field.
* If the user pastes the OTP (e.g. “1234”), each box should fill with a digit.
* Once all 4 digits are entered, trigger a callback onChangeTP and send the full OTP string.
* Disallow any non-numeric input.

https://namastedev.com/practice/otp-input-component

A

styles.css

body {
  font-family: sans-serif;
  -webkit-font-smoothing: auto;
  -moz-font-smoothing: auto;
  -moz-osx-font-smoothing: grayscale;
  font-smoothing: auto;
  text-rendering: optimizeLegibility;
  font-smooth: always;
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;
}

h1 {
  font-size: 1.5rem;
}

OTPInput.js

import React, { useRef, useState } from "react";
import "../styles.css";

function OTPInput({ onChangeOTP }) {
  const length = 4;
  const [otp, setOTP] = useState(Array(length).fill(""));
  const inputsRef = useRef([]);

  const focusInput = (index) => {
    if (inputsRef.current[index]) {
      inputsRef.current[index].focus();
    }
  };

  const handleChange = (e, index) => {
    const value = e.target.value;
    if (!/^\d?$/.test(value)) return; // Reject non-numeric input

    const updatedOTP = [...otp];
    updatedOTP[index] = value;
    setOTP(updatedOTP);

    if (value && index < length - 1) {
      focusInput(index + 1);
    }

    const otpValue = updatedOTP.join("");
    if (otpValue.length === length && !updatedOTP.includes("")) {
      onChangeOTP(otpValue);
    }
  };

  const handleKeyDown = (e, index) => {
    if (e.key === "Backspace") {
      if (otp[index] === "") {
        if (index > 0) focusInput(index - 1);
      }
    } else if (e.key === "ArrowLeft") {
      if (index > 0) focusInput(index - 1);
    } else if (e.key === "ArrowRight") {
      if (index < length - 1) focusInput(index + 1);
    }
  };

  const handlePaste = (e) => {
    e.preventDefault();
    const pasted = e.clipboardData.getData("Text");
    const digits = pasted.replace(/\D/g, "").split("").slice(0, length);

    if (digits.length === 0) return;

    const updatedOTP = [...otp];
    for (let i = 0; i < digits.length; i++) {
      updatedOTP[i] = digits[i];
      if (inputsRef.current[i]) {
        inputsRef.current[i].value = digits[i];
      }
    }
    setOTP(updatedOTP);

    const nextIndex = digits.length < length ? digits.length : length - 1;
    focusInput(nextIndex);

    const otpValue = updatedOTP.join("");
    if (otpValue.length === length && !updatedOTP.includes("")) {
      onChangeOTP(otpValue);
    }
  };

  return (
    <div onPaste={handlePaste}>
      {otp.map((digit, index) => (
        <input
          key={index}
          ref={(el) => (inputsRef.current[index] = el)}
          type="text"
          maxLength="1"
          inputMode="numeric"
          value={digit}
          onChange={(e) => handleChange(e, index)}
          onKeyDown={(e) => handleKeyDown(e, index)}
          style={{
            width: "40px",
            height: "40px",
            fontSize: "20px",
            textAlign: "center",
            marginRight: "10px",
          }}
        />
      ))}
    </div>
  );
}

export default OTPInput;

App.js

import OTPInput from "./OTPInput";

export default function App() {
  const handleOTPChange = (otp) => {
    console.log("Entered OTP:", otp);
  };

  return <OTPInput onChangeOTP={handleOTPChange} />;
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

Match Pair Game

Description
Create a memory matching game where players flip cards to find matching pairs in a 4x4 grid (8 pairs total). The game should track flips, matches, and moves, and provide visual feedback during gameplay.

Requirements
1. Card Grid
* Render a 4x4 grid (total 16 cards).
* Each card hides an emoji initially.
* Use these 8 emojis (each repeated twice):
['❀️','πŸ€','🌎','🍎','⚽️','πŸš—','⛡️','πŸ’Ž']
* Randomly shuffle emojis on every game start/reset.
2. Card Behavior
* When a card is clicked:
1. Reveal the emoji.
2.Only two cards can be revealed at a time.
* If the two revealed cards match, they stay visible (marked as matched).
* If they don’t match, flip them back after a 1-second delay.
* Disable user interaction during this 1-second delay.
3. Game Logic
* Track and display the number of moves (every two flips = one move).
* When all pairs are matched:
Show a “You won!” message.
4. Reset Button
* Provide a Reset button to restart the game:
1. Shuffle the emojis.
2. Reset the move counter.
3. Hide all cards.
4. Clear any matched state or win message.

https://namastedev.com/practice/match-pair-game

A

MatchpairGame.jsx

import React, { useState, useEffect } from 'react';
import './MatchPairGame.css';

const initialEmojis = ['❀️', 'πŸ€', '🌎', '🍎', '⚽️', 'πŸš—', '⛡️', 'πŸ’Ž'];

const shuffleEmojis = () => {
  const paired = [...initialEmojis, ...initialEmojis];
  for (let i = paired.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [paired[i], paired[j]] = [paired[j], paired[i]];
  }
  return paired.map((emoji, index) => ({
    id: index,
    value: emoji,
    revealed: false,
    matched: false,
  }));
};

const MatchPairGame = () => {
  const [cards, setCards] = useState([]);
  const [firstCard, setFirstCard] = useState(null);
  const [secondCard, setSecondCard] = useState(null);
  const [moves, setMoves] = useState(0);
  const [won, setWon] = useState(false);
  const [disabled, setDisabled] = useState(false);

  useEffect(() => {
    setCards(shuffleEmojis());
  }, []);

  useEffect(() => {
    if (firstCard && secondCard) {
      setDisabled(true);
      setMoves((prev) => prev + 1);

      if (firstCard.value === secondCard.value) {
        // Match found
        setCards((prevCards) =>
          prevCards.map((card) =>
            card.id === firstCard.id || card.id === secondCard.id
              ? { ...card, matched: true }
              : card
          )
        );
        resetTurn();
      } else {
        // No match: flip back after delay
        setTimeout(() => {
          setCards((prevCards) =>
            prevCards.map((card) =>
              card.id === firstCard.id || card.id === secondCard.id
                ? { ...card, revealed: false }
                : card
            )
          );
          resetTurn();
        }, 1000);
      }
    }
  }, [firstCard, secondCard]);

  useEffect(() => {
    const allMatched = cards.length > 0 && cards.every((card) => card.matched);
    if (allMatched) setWon(true);
  }, [cards]);

  const handleClick = (card) => {
    if (disabled || card.revealed || card.matched) return;

    const updatedCards = cards.map((c) =>
      c.id === card.id ? { ...c, revealed: true } : c
    );
    setCards(updatedCards);

    if (!firstCard) {
      setFirstCard(card);
    } else if (!secondCard) {
      setSecondCard(card);
    }
  };

  const resetTurn = () => {
    setFirstCard(null);
    setSecondCard(null);
    setDisabled(false);
  };

  const resetGame = () => {
    setCards(shuffleEmojis());
    setFirstCard(null);
    setSecondCard(null);
    setMoves(0);
    setWon(false);
    setDisabled(false);
  };

  return (
    <div className="game-container">
      <h1>Match Pair Game</h1>
      <div className="grid">
        {cards.map((card) => (
          <div
            key={card.id}
            className={`card ${card.revealed || card.matched ? 'revealed' : ''} ${
              card.matched ? 'matched' : ''
            }`}
            onClick={() => handleClick(card)}
          >
            {(card.revealed || card.matched) && card.value}
          </div>
        ))}
      </div>
      <p>Moves: {moves}</p>
      {won && <p className="won">πŸŽ‰ You won!</p>}
      <button onClick={resetGame}>Reset</button>
    </div>
  );
};

export default MatchPairGame;

MatchPairgame.css

/* styles.css */
.game-container {
  text-align: center;
  padding: 20px;
}

.grid {
  display: grid;
  grid-template-columns: repeat(4, 80px);
  gap: 10px;
  justify-content: center;
}

.card {
  width: 80px;
  height: 80px;
  background-color: #ccc;
  font-size: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  border-radius: 8px;
  user-select: none;
}

.card.matched {
  background-color: #8bc34a;
  cursor: default;
}

.card.revealed {
  background-color: #fff;
}

App.js

import MatchPairGame from './MatchPairGame'

export default function App() {
  return <MatchPairGame/>
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

StickyNotes Component

Create a sticky notes app using React that allows users to add, edit, drag, and remove colorful sticky notes inside a container.

Requirements
* Render a container area boundary for sticky notes.
* Display a fixed add button “+” in the right bottom corner of the screen which will add a new note when clicked. Use lucide-icon “Plus”
* Sticky Notes Display
Each sticky note is a draggable colored block with a textarea for text input.
With a placeholder “Enter Text”
Notes should have random pastel colors chosen from a predefined set.
Position notes in a grid layout initially (3 columns).
The note being dragged should follow mouse movement inside the container.
A close button on each note removes it.Use lucide-icon “X”
Clicking Textarea inside a note allows editing note content.
Scrolling inside the container should scroll content as normal.
Notes do not overlap on creation - new notes get the next position in rows/columns.
* Drag & Drop Behavior
Clicking and dragging a note moves it around.
Dragging is disabled when interacting inside the textarea.
Mouse release stops dragging and fixes note position.
The last interacted note should render on top of other notes.

Edge Cases & Constraints
* Notes cannot be dragged outside container bounds.
* Textarea interactions do not trigger dragging.
* Container scrolls automatically to bottom when new note is added.
* Notes maintain their positions when dragged and after page re-render.
* Notes start aligned in a 3-column grid with gaps.
* Initial note position is (0,0) and adjusted automatically on layout.
* Adding/removing notes updates layout and state properly.

Testing Requirements
1. Data Test IDs (required for testing):
* data-testid=”sticky-notes-container” - The container for sticky
notes
* data-testid=”sticky-note” - Each sticky note
* data-testid=”close-button” - Close button on a note
* data-testid=”note-textarea” - Textarea inside a note
* data-testid=”add-note-button” - Button to add a new note
* data-testid=”icon-close” - Lucide X icon inside the close button.
* data-testid=”icon-add” - Lucide Plus icon inside the add button.

https://namastedev.com/practice/stickynotes-component

A

styles.css

.container {
  width: 600px;
  min-height: 550px;
  border: 2px solid #000000;
  overflow-y: auto;
  margin: 0 auto;
  padding: 30px;
  background-color: #fefefe;
}

.note {
  position: absolute;
  margin: auto;
  width: 170px;
  height: 150px;
  box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
  border-radius: 2px;
  padding: 8px;
  box-sizing: border-box;
  cursor: grab;
  user-select: none;
  transition: box-shadow 0.3s ease;
}

.note:hover {
  box-shadow: 0 10px 18px rgba(0, 0, 0, 0.25);
}

.note-textarea {
  width: 90%;
  height: 100%;
  border: none;
  resize: none;
  background: transparent;
  outline: none;
  font-size: 14px;
  font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
  color: #333;
}

.close-btn {
  position: absolute;
  top: 6px;
  right: 6px;
  border: none;
  background: transparent;
  font-weight: bold;
  cursor: pointer;
  font-size: 16px;
  line-height: 16px;
  user-select: none;
  transition: transform 0.2s ease;
}

.close-btn:hover .icon-close {
  transform: rotate(90deg);
  color: #d9534f;
}

.add-note-btn {
  position: fixed;
  bottom: 30px;
  right: 30px;
  width: 50px;
  height: 50px;
  border-radius: 50%;
  background-color: #063c68;
  font-size: 30px;
  border: none;
  cursor: pointer;
  box-shadow: 0 6px 12px rgba(0, 0, 0, 0.25);
  user-select: none;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  transition: background-color 0.3s ease, transform 0.2s ease;
}

.add-note-btn:hover {
  background-color: #085197;
  transform: scale(1.05);
}

/* Icon Styling */
.icon-add {
  color: #ffffff;
  transition: transform 0.2s ease, color 0.2s ease;
}

.add-note-btn:hover .icon-add {
  transform: rotate(90deg);
  color: #e0e0e0;
}

.icon-close {
  color: #333;
  transition: transform 0.2s ease, color 0.2s ease;
}

App.js

import StickyNote from "./StickyNote";
export default function App() {
  return <StickyNote />;
}

StickyNote.js

import React, { useState, useRef, useEffect } from "react";
import { Plus, X } from "lucide-react";
import "./styles.css";

const PASTEL_COLORS = [
  "#FFB3BA", "#FFDFBA", "#FFFFBA",
  "#BAFFC9", "#BAE1FF"
];

export default function StickyNote() {
  const containerRef = useRef(null);
  const [notes, setNotes] = useState([]);
  const [draggingNoteId, setDraggingNoteId] = useState(null);
  const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
  const [zIndexCounter, setZIndexCounter] = useState(1);

  // Add new note
  const addNote = () => {
    const container = containerRef.current;
    if (!container) return;

    const colCount = 3;
    const noteWidth = 170;
    const noteHeight = 150;
    const gap = 16;

    const index = notes.length;
    const col = index % colCount;
    const row = Math.floor(index / colCount);

    const newNote = {
      id: Date.now(),
      text: "",
      color: PASTEL_COLORS[Math.floor(Math.random() * PASTEL_COLORS.length)],
      x: col * (noteWidth + gap),
      y: row * (noteHeight + gap),
      zIndex: zIndexCounter
    };

    setNotes(prev => [...prev, newNote]);
    setZIndexCounter(prev => prev + 1);

    // Scroll container to bottom
    setTimeout(() => {
      container.scrollTop = container.scrollHeight;
    }, 0);
  };

  // Remove a note
  const removeNote = (id) => {
    setNotes(prev => prev.filter(note => note.id !== id));
  };

  // Update note text
  const updateNoteText = (id, text) => {
    setNotes(prev =>
      prev.map(note => note.id === id ? { ...note, text } : note)
    );
  };

  // Mouse event handlers for dragging
  const handleMouseDown = (e, id) => {
    // Prevent dragging when clicking inside textarea
    if (e.target.tagName === "TEXTAREA") return;

    const note = notes.find(n => n.id === id);
    if (!note) return;

    const rect = e.target.getBoundingClientRect();
    setDragOffset({
      x: e.clientX - note.x,
      y: e.clientY - note.y
    });
    setDraggingNoteId(id);
    // Bring note to top
    setNotes(prev =>
      prev.map(n => n.id === id ? { ...n, zIndex: zIndexCounter } : n)
    );
    setZIndexCounter(prev => prev + 1);
  };

  const handleMouseMove = (e) => {
    if (draggingNoteId === null) return;

    const container = containerRef.current;
    const containerRect = container.getBoundingClientRect();

    setNotes(prev =>
      prev.map(note => {
        if (note.id !== draggingNoteId) return note;

        let newX = e.clientX - dragOffset.x;
        let newY = e.clientY - dragOffset.y;

        // Boundaries
        newX = Math.max(0, Math.min(newX, container.clientWidth - 170));
        newY = Math.max(0, Math.min(newY, container.scrollHeight - 150));

        return { ...note, x: newX, y: newY };
      })
    );
  };

  const handleMouseUp = () => {
    setDraggingNoteId(null);
  };

  useEffect(() => {
    window.addEventListener("mousemove", handleMouseMove);
    window.addEventListener("mouseup", handleMouseUp);

    return () => {
      window.removeEventListener("mousemove", handleMouseMove);
      window.removeEventListener("mouseup", handleMouseUp);
    };
  });

  return (
    <div>
      <div
        className="container"
        ref={containerRef}
        data-testid="sticky-notes-container"
      >
        {notes.map(note => (
          <div
            key={note.id}
            className="note"
            data-testid="sticky-note"
            style={{
              left: note.x,
              top: note.y,
              backgroundColor: note.color,
              zIndex: note.zIndex
            }}
            onMouseDown={(e) => handleMouseDown(e, note.id)}
          >
            <button
              className="close-btn"
              onClick={() => removeNote(note.id)}
              data-testid="close-button"
            >
              <X className="icon-close" data-testid="icon-close" />
            </button>
            <textarea
              className="note-textarea"
              placeholder="Enter Text"
              value={note.text}
              onChange={(e) => updateNoteText(note.id, e.target.value)}
              data-testid="note-textarea"
            />
          </div>
        ))}
      </div>

      <button
        className="add-note-btn"
        onClick={addNote}
        data-testid="add-note-button"
      >
        <Plus className="icon-add" data-testid="icon-add" />
      </button>
    </div>
  );
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

Nested Comments

Requirements
* Users should be able to add new top-level comments.
* Enter text in the top-level comment input (data-testid=”new-comment-input”).
* Click Add Comment (data-testid=”add-comment-btn”) to post.
* Users should be able to reply to any existing comment, supporting infinite nesting.
* Each comment shows an Add a reply button (data-testid=” reply-btn-(id}”).
* Clicking the button opens:
* A reply input (data-testid=”reply-input-{id}”).
* A submit button (data-testid=”submit-reply-{id}”).
* Each comment can have its own reply box toggled independently.
* Replies must be displayed nested under their parent comment, inside the comment container (data-testid=”comment-{id}”).
* The system must maintain herarchical order:
* Replying to a comment places the reply directly under it.
* Replying to a reply nests under that reply, and so on.
* All interactive and key display elements must include data-testid attributes for reliable automated testing.

Edge Cases & Constraints
* Empty input must not be allowed - clicking submit with blank/whitespace-only text should not add a comment or reply.
* Multiple replies to the same comment should be supported without overwriting existing ones.
* Must handle deep nesting without Ul breaking or recursion errors (tested with nested reply).
* Reply box state isolation - opening a reply box for one comment should not affect others.
* Unique IDs:
All comments and replies must have unique numeric IDs.
These IDs are also used in data-testid attributes for targeting in tests.

Test cases criteria
* The top-level comment input field must have data-testid=”new-comment-input”.
* The “Add Comment” button must have data-testid=”add-comment-btn”.
* Every comment container must have a unique data-testid in the format comment-{id}.
* Every reply button must have a unique data-testid in the format reply-btn-(id).
* Every reply input field must have a unique data-testid in the format reply-input-(id}.
* Every submit reply button must have a unique data-testid in the format submit-reply-(id).
* Use mock.json to preload comments when the app loads.
* mock.json should contain an array of top-level comments, each with: id (unique number or string) text (string) replies (array of nested comment objects with same structure)
* This ensures test cases can start with existing nested comments for verification.

https://namastedev.com/practice/nested-comments

A

CommentApp.js

import React, { useState } from "react";
import "./styles.css";

// Mock Comment Data (initial state)
const mockComments = [
  {
    id: 1,
    text: "Happy New Year folks! What are your resolutions this year?",
    replies: [
      {
        id: 2,
        text: "Same to you. I am planning to join a gym.",
        replies: [
          {
            id: 3,
            text: "I tried last year and gave up.",
            replies: [
              {
                id: 4,
                text: "Good on you, nothing is more important than good health.",
                replies: [],
              },
            ],
          },
        ],
      },
    ],
  },
];

// Counter for generating unique IDs
let idCounter = 4;

export default function CommentApp() {
  const [comments, setComments] = useState(mockComments);
  const [newComment, setNewComment] = useState("");

  // Function to add a reply to a nested comment
  const addReply = (parentId, text) => {
    if (!text.trim()) return; // Prevent empty reply

    const addNestedReply = (commentList) => {
      return commentList.map((comment) => {
        if (comment.id === parentId) {
          idCounter += 1;
          return {
            ...comment,
            replies: [
              ...comment.replies,
              { id: idCounter, text, replies: [] },
            ],
          };
        }
        if (comment.replies.length > 0) {
          return { ...comment, replies: addNestedReply(comment.replies) };
        }
        return comment;
      });
    };

    setComments((prev) => addNestedReply(prev));
  };

  // Function to add top-level comment
  const addComment = () => {
    if (!newComment.trim()) return; // Prevent empty comment
    idCounter += 1;
    setComments((prev) => [
      ...prev,
      { id: idCounter, text: newComment, replies: [] },
    ]);
    setNewComment("");
  };

  // Recursive Comment Component
  const Comment = ({ comment }) => {
    const [showReplyInput, setShowReplyInput] = useState(false);
    const [replyText, setReplyText] = useState("");

    const handleReply = () => {
      if (!replyText.trim()) return;
      addReply(comment.id, replyText);
      setReplyText("");
      setShowReplyInput(false);
    };

    return (
      <div className="comment" data-testid={`comment-${comment.id}`}>
        <div>{comment.text}</div>

        <button
          onClick={() => setShowReplyInput(!showReplyInput)}
          data-testid={`reply-btn-${comment.id}`}
        >
          Add a reply
        </button>

        {showReplyInput && (
          <div className="reply-box">
            <input
              type="text"
              value={replyText}
              onChange={(e) => setReplyText(e.target.value)}
              placeholder="Type your reply..."
              data-testid={`reply-input-${comment.id}`}
            />
            <button
              onClick={handleReply}
              data-testid={`submit-reply-${comment.id}`}
            >
              Submit
            </button>
          </div>
        )}

        <div className="replies">
          {comment.replies.map((reply) => (
            <Comment key={reply.id} comment={reply} />
          ))}
        </div>
      </div>
    );
  };

  return (
    <div className="App">
      <h2>Comment Section</h2>
      <div className="new-comment">
        <input
          type="text"
          value={newComment}
          onChange={(e) => setNewComment(e.target.value)}
          placeholder="Type a comment..."
          data-testid="new-comment-input"
        />
        <button onClick={addComment} data-testid="add-comment-btn">
          Add Comment
        </button>
      </div>

      <div className="comments">
        {comments.map((comment) => (
          <Comment key={comment.id} comment={comment} />
        ))}
      </div>
    </div>
  );
}

App.js

import React from "react";
import CommentApp from "./CommentApp";

export default function App() {
  return <CommentApp />;
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

Tree Navigation

The Tree / Folder Navigation with Checkboxes is a React component that displays a hierarchical folder structure with checkboxes for each folder and file. Users can expand/collapse folders and toggle checkboxes to select/deselect items. Selecting a folder automatically selects/deselects all its children, and child selections affect parent checkbox states (e.g., a parent is checked if all children are checked).

Things To Do
1. Hierarchical Display:
Render a tree of folders and files, with folders expandable/collapsible.
2. Title Rendering:
Display a title “Folder Navigation”.
3. Data as Prop:
Tree data is passed as a prop from App. js and must not be modified, as tests rely on the structure.
4. Checkboxes:
Each folder and file has a checkbox for selection.
5. Parent-Child Sync:
Checking a folder selects/deselects all its children and child selections update parent state.
6. Interactivity:
Support toggling folder expansion and checkbox states.
7. Testability:
* Include data-testid attributes for: tree-container: The root container. node-${id}: Each folder/file node. checkbox-${id}: Each checkbox. toggle-${id}: Folder toggle buttons.

https://namastedev.com/practice/tree-navigation

A

styles.css

body {
  margin: 0;
  font-family: Arial, sans-serif;
}

.tree-container {
  text-align: left;
  min-height: 100vh;
  background-color: #f0f0f0;
  padding: 20px;
}

h1 {
  font-size: 32px;
  margin-bottom: 20px;
  text-align: center;
}

.node {
  margin-left: 20px;
}

.node-content {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 5px 0;
}

.folder {
  font-weight: bold;
}

.file {
  font-style: italic;
}

.children {
  margin-left: 20px;
}

button {
  background: none;
  border: none;
  cursor: pointer;
  font-size: 14px;
}

App.js

import React from "react";
import TreeNavigation from "./TreeNavigation";

const initialTree = [
  {
    id: "1",
    name: "Root",
    type: "folder",
    children: [
      { id: "2", name: "File1.txt", type: "file" },
      {
        id: "3",
        name: "Folder1",
        type: "folder",
        children: [{ id: "4", name: "File2.txt", type: "file" }],
      },
    ],
  },
];

export default function App() {
  return <TreeNavigation tree={initialTree} />;
}

TreeNavigation.js

import React, { useState } from "react";
import "./styles.css";

// Recursive TreeNode component
function TreeNode({ node, selectedIds, toggleCheckbox, toggleFolder, expandedIds }) {
  const isFolder = node.type === "folder";
  const isExpanded = expandedIds.has(node.id);

  return (
    <div className={`node ${node.type}`} data-testid={`node-${node.id}`}>
      <div className="node-content">
        {isFolder && (
          <button
            onClick={() => toggleFolder(node.id)}
            data-testid={`toggle-${node.id}`}
          >
            {isExpanded ? "β–Ό" : "β–Ά"}
          </button>
        )}
        <input
          type="checkbox"
          checked={selectedIds.has(node.id)}
          onChange={() => toggleCheckbox(node, !selectedIds.has(node.id))}
          data-testid={`checkbox-${node.id}`}
        />
        <span>{node.name}</span>
      </div>
      {isFolder && isExpanded && node.children && (
        <div className="children">
          {node.children.map((child) => (
            <TreeNode
              key={child.id}
              node={child}
              selectedIds={selectedIds}
              toggleCheckbox={toggleCheckbox}
              toggleFolder={toggleFolder}
              expandedIds={expandedIds}
            />
          ))}
        </div>
      )}
    </div>
  );
}

// Main TreeNavigation component
function TreeNavigation({ tree }) {
  const [selectedIds, setSelectedIds] = useState(new Set());
  const [expandedIds, setExpandedIds] = useState(new Set(["1"])); // Root expanded by default

  // Expand / Collapse folder
  const toggleFolder = (id) => {
    setExpandedIds((prev) => {
      const newSet = new Set(prev);
      if (newSet.has(id)) {
        newSet.delete(id);
      } else {
        newSet.add(id);
      }
      return newSet;
    });
  };

  // Helper: get all descendant IDs of a node
  const getAllDescendantIds = (node) => {
    let ids = [];
    if (node.children) {
      for (const child of node.children) {
        ids.push(child.id);
        if (child.type === "folder") {
          ids = ids.concat(getAllDescendantIds(child));
        }
      }
    }
    return ids;
  };

  // Helper: find parent ID(s) of a node
  const findParentIds = (nodeId, nodes, parents = []) => {
    for (const node of nodes) {
      if (node.children && node.children.some((child) => child.id === nodeId)) {
        parents.push(node.id);
        findParentIds(node.id, tree, parents);
      }
      if (node.children) {
        findParentIds(nodeId, node.children, parents);
      }
    }
    return parents;
  };

  // Toggle checkbox selection
  const toggleCheckbox = (node, checked) => {
    setSelectedIds((prev) => {
      const newSet = new Set(prev);

      // Add or remove node itself
      if (checked) {
        newSet.add(node.id);
      } else {
        newSet.delete(node.id);
      }

      // Handle all descendants
      if (node.type === "folder") {
        const descendants = getAllDescendantIds(node);
        descendants.forEach((id) => {
          if (checked) newSet.add(id);
          else newSet.delete(id);
        });
      }

      // Update parent checkboxes
      const parentIds = findParentIds(node.id, tree);
      parentIds.forEach((parentId) => {
        const parentNode = findNodeById(parentId, tree);
        if (parentNode) {
          const allChildrenSelected = parentNode.children.every((child) =>
            newSet.has(child.id)
          );
          if (allChildrenSelected) newSet.add(parentId);
          else newSet.delete(parentId);
        }
      });

      return newSet;
    });
  };

  // Helper: find node by ID
  const findNodeById = (id, nodes) => {
    for (const node of nodes) {
      if (node.id === id) return node;
      if (node.children) {
        const found = findNodeById(id, node.children);
        if (found) return found;
      }
    }
    return null;
  };

  return (
    <div className="tree-container" data-testid="tree-container">
      <h1>Folder Navigation</h1>
      {tree.map((node) => (
        <TreeNode
          key={node.id}
          node={node}
          selectedIds={selectedIds}
          toggleCheckbox={toggleCheckbox}
          toggleFolder={toggleFolder}
          expandedIds={expandedIds}
        />
      ))}
    </div>
  );
}

export default TreeNavigation;
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

Event Management

Problem Description
Build a simple calendar application in React that allows users to:
* View a monthly calendar with dates in a grid.
* Add events (with title and date in YYYY-MM-DD format).
* Display multiple events on each day.
* Delete events when needed.
* Navigate between previous and next months.
* Highlight today’s date with a distinct style.
* Validate user input so events cannot be saved without a title or date.
The application should be functional, clean, and written without external dependencies (only React).

Functional Requirements
1. Calendar Display
Show current month name and year in the header.
Month navigation should use β€Ή (previous) and β€Ί (next) buttons.
Dates should be shown in a grid, starting with Sunday as the first day of the week.
2. Event Creation
Provide an Add Event button below the header.
Clicking it should open a modal with:
Input for Event Title
Input for Event Date (YYYY-MM-DD format)
The modal should also have Save, Cancel, and X (close) buttons.
3. Event Display
Events should be displayed inside their respective day cells.
Each event should be listed with a delete button (x) next to it.
4. Event Deletion
Clicking the delete button should remove that event from the calendar.
5. Validation
If the user clicks Save with an empty title β†’ show
“Please enter event title”.
If the user clicks Save with no date β†’ show “Please select event date”.
The error message should be displayed inside the modal above the form.
6. Highlighting Today
The current date should be styled differently (e.g., bold background or border).

Data Test ID
* calendar-container β†’ Wrapper of the calendar app.
* prev-month-bt β†’ Header navigation left button (β€Ή).
* next-month-btn - Header navigation right button (*).
* month-year-display β†’ Header text showing
“August 2025” etc.
* add-event-btn β†’ Button below header to open modal.
* event-modal β†’ Modal wrapper.
* close-modal-bt β†’ Top-right close button in modal.
* event-title-input β†’ Input for event title.
* event-date-input β†’ Input for event date.
* save-event-bt β†’ Save button in modal.
* validation-error β†’ Error text inside modal.
* event-item β†’ Each displayed event inside a day.
* delete-event-btn β†’ Button inside each event item.

https://namastedev.com/practice/event-management

A

styles.css

.calendar-app {
  max-width: 800px;
  margin: 20px auto;
  font-family: Arial, sans-serif;
  padding: 20px;
}

.calendar-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
}

.nav-btn {
  background: #007bff;
  color: white;
  border: none;
  padding: 10px 15px;
  border-radius: 5px;
  cursor: pointer;
  font-size: 18px;
}

.nav-btn:hover {
  background: #0056b3;
}

.add-event-btn {
  background: #28a745;
  color: white;
  border: none;
  padding: 10px 20px;
  border-radius: 5px;
  cursor: pointer;
  margin-bottom: 20px;
}

.add-event-btn:hover {
  background: #1e7e34;
}

.calendar-grid {
  border: 1px solid #ddd;
  border-radius: 5px;
}

.weekdays {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  background: #f8f9fa;
}

.weekday {
  padding: 10px;
  text-align: center;
  font-weight: bold;
  border-right: 1px solid #ddd;
}

.weekday:last-child {
  border-right: none;
}

.days-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
}

.calendar-day {
  min-height: 100px;
  padding: 5px;
  border-right: 1px solid #ddd;
  border-bottom: 1px solid #ddd;
  position: relative;
}

.calendar-day:nth-child(7n) {
  border-right: none;
}

.calendar-day.today {
  background: #e3f2fd;
}

.calendar-day.empty {
  background: #f8f9fa;
}

.day-number {
  font-weight: bold;
  display: block;
  margin-bottom: 5px;
}

.events-container {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.event-item {
  background: #007bff;
  color: white;
  padding: 2px 5px;
  border-radius: 3px;
  font-size: 12px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.delete-btn {
  background: none;
  border: none;
  color: white;
  cursor: pointer;
  font-size: 14px;
  padding: 0;
  margin-left: 5px;
}

.delete-btn:hover {
  color: #ff6b6b;
}

.modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1000;
}

.modal {
  background: white;
  border-radius: 5px;
  width: 400px;
  max-width: 90vw;
}

.modal-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 15px;
  border-bottom: 1px solid #ddd;
}

.close-btn {
  background: none;
  border: none;
  font-size: 20px;
  cursor: pointer;
}

.modal-body {
  padding: 15px;
}

.form-group {
  margin-bottom: 15px;
}

.form-group label {
  display: block;
  margin-bottom: 5px;
  font-weight: bold;
}

.form-group input {
  width: 100%;
  padding: 8px;
  border: 1px solid #ddd;
  border-radius: 3px;
  box-sizing: border-box;
}

.modal-footer {
  padding: 15px;
  border-top: 1px solid #ddd;
  display: flex;
  gap: 10px;
  justify-content: flex-end;
}

.save-btn {
  background: #28a745;
  color: white;
  border: none;
  padding: 8px 16px;
  border-radius: 3px;
  cursor: pointer;
}

.cancel-btn {
  background: #6c757d;
  color: white;
  border: none;
  padding: 8px 16px;
  border-radius: 3px;
  cursor: pointer;
}

.save-btn:hover {
  background: #1e7e34;
}

.error-message {
  background: #f8d7da;
  color: #721c24;
  padding: 10px;
  border-radius: 3px;
  margin-bottom: 15px;
  border: 1px solid #f5c6cb;
}

.cancel-btn:hover {
  background: #545b62;
}

App.js

import CalendarApp from './CalendarApp.js'
export default function App() {
  return <CalendarApp/>
}

CalendarApp.js

import { useState } from "react";
import "./styles.css";

const CalendarApp = () => {
  const [currentDate, setCurrentDate] = useState(new Date());
  const [events, setEvents] = useState({});
  const [showModal, setShowModal] = useState(false);
  const [eventTitle, setEventTitle] = useState("");
  const [eventDate, setEventDate] = useState("");
  const [validationError, setValidationError] = useState("");

  const monthNames = [
    "January",
    "February",
    "March",
    "April",
    "May",
    "June",
    "July",
    "August",
    "September",
    "October",
    "November",
    "December",
  ];

  const currentMonth = currentDate.getMonth();
  const currentYear = currentDate.getFullYear();
  const today = new Date();

  // Calculate number of days in the current month
  const daysInMonth = new Date(currentYear, currentMonth + 1, 0).getDate();
  // Day of week the month starts on (0 = Sunday)
  const firstDayOfMonth = new Date(currentYear, currentMonth, 1).getDay();

  const goToPreviousMonth = () => {
    setCurrentDate((prev) => {
      const prevMonth = new Date(prev.getFullYear(), prev.getMonth() - 1, 1);
      return prevMonth;
    });
  };

  const goToNextMonth = () => {
    setCurrentDate((prev) => {
      const nextMonth = new Date(prev.getFullYear(), prev.getMonth() + 1, 1);
      return nextMonth;
    });
  };

  const openAddEventModal = () => {
    setEventTitle("");
    setEventDate("");
    setValidationError("");
    setShowModal(true);
  };

  const closeModal = () => {
    setShowModal(false);
    setValidationError("");
  };

  const saveEvent = () => {
    if (!eventTitle.trim()) {
      setValidationError("Please enter event title");
      return;
    }
    if (!eventDate) {
      setValidationError("Please select event date");
      return;
    }

    setEvents((prev) => {
      const newEvents = { ...prev };
      if (!newEvents[eventDate]) newEvents[eventDate] = [];
      newEvents[eventDate].push({ id: Date.now(), title: eventTitle });
      return newEvents;
    });

    setShowModal(false);
  };

  const deleteEvent = (eventId, date) => {
    setEvents((prev) => {
      const newEvents = { ...prev };
      newEvents[date] = newEvents[date].filter((e) => e.id !== eventId);
      if (newEvents[date].length === 0) delete newEvents[date];
      return newEvents;
    });
  };

  const isToday = (day) => {
    return (
      day === today.getDate() &&
      currentMonth === today.getMonth() &&
      currentYear === today.getFullYear()
    );
  };

  const getEventsForDay = (day) => {
    const dateStr = `${currentYear}-${String(currentMonth + 1).padStart(
      2,
      "0"
    )}-${String(day).padStart(2, "0")}`;
    return events[dateStr] || [];
  };

  const renderCalendarDays = () => {
    const days = [];

    // Add empty cells for days before first day of month
    for (let i = 0; i < firstDayOfMonth; i++) {
      days.push(
        <div key={`empty-${i}`} className="calendar-day empty"></div>
      );
    }

    for (let day = 1; day <= daysInMonth; day++) {
      const dayEvents = getEventsForDay(day);
      days.push(
        <div
          key={day}
          className={`calendar-day ${isToday(day) ? "today" : ""}`}
        >
          <span className="day-number">{day}</span>
          <div className="events-container">
            {dayEvents.map((event) => (
              <div key={event.id} className="event-item" data-testid="event-item">
                {event.title}
                <button
                  className="delete-btn"
                  data-testid="delete-event-btn"
                  onClick={() => deleteEvent(event.id, `${currentYear}-${String(currentMonth + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`)}
                >
                  x
                </button>
              </div>
            ))}
          </div>
        </div>
      );
    }

    return days;
  };

  return (
    <div className="calendar-app" data-testid="calendar-container">
      <div className="calendar-header">
        <button
          data-testid="prev-month-btn"
          className="nav-btn"
          onClick={goToPreviousMonth}
        >
          &#8249;
        </button>
        <span data-testid="month-year-display">
          {monthNames[currentMonth]} {currentYear}
        </span>
        <button
          data-testid="next-month-btn"
          className="nav-btn"
          onClick={goToNextMonth}
        >
          &#8250;
        </button>
      </div>

      <button
        data-testid="add-event-btn"
        className="add-event-btn"
        onClick={openAddEventModal}
      >
        \+ Add Event
      </button>

      <div className="calendar-grid">
        <div className="weekdays">
          {["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].map((d) => (
            <div key={d} className="weekday">
              {d}
            </div>
          ))}
        </div>

        <div className="days-grid">{renderCalendarDays()}</div>
      </div>

      {showModal && (
        <div className="modal-overlay" data-testid="event-modal">
          <div className="modal">
            <div className="modal-header">
              <h3>Add Event</h3>
              <button
                className="close-btn"
                data-testid="close-modal-bt"
                onClick={closeModal}
              >
                &times;
              </button>
            </div>
            <div className="modal-body">
              {validationError && (
                <div className="error-message" data-testid="validation-error">
                  {validationError}
                </div>
              )}
              <div className="form-group">
                <label>Event Title</label>
                <input
                  data-testid="event-title-input"
                  value={eventTitle}
                  onChange={(e) => setEventTitle(e.target.value)}
                  placeholder="Event title"
                />
              </div>
              <div className="form-group">
                <label>Event Date</label>
                <input
                  type="date"
                  data-testid="event-date-input"
                  value={eventDate}
                  onChange={(e) => setEventDate(e.target.value)}
                />
              </div>
            </div>
            <div className="modal-footer">
              <button
                className="save-btn"
                data-testid="save-event-bt"
                onClick={saveEvent}
              >
                Save
              </button>
              <button className="cancel-btn" onClick={closeModal}>
                Cancel
              </button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
};

export default CalendarApp;
How well did you know this?
1
Not at all
2
3
4
5
Perfectly