Hard Flashcards

Hard level React coding assignments (7 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

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 './styles.css'

const App = () => (
   <>
    <nav className="navbar">    
    </nav>
    <Routes>
    </Routes>
  </>
);

export default App;

Home.jsx

import React from "react";

const Home = () => {
  return (
    <div className="home">
    </div>
  )
};

export default Home;

Products.jsx

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

const Products = () => {
 

  return (
    <div className="products">
      <h2>Product List</h2>
      <div className="product-list">
      </div>
    </div>
  );
};

export default Products;

ProductDetails.jsx

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

const ProductDetails = () => {

  return (
    <div className="product-details">
      
    </div>
  );
};

export default ProductDetails;

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

styles.css

```

App.js

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

Pagination.js

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

const PAGE_SIZE = 10;

const Pagination = () => {
 return(
    <div>
      <h1>Pagination</h1>
        </div>
 )
};
export default Pagination;

ProductCard.js

const ProductCard= ({image,title})=>{
  return (
    <div>
    </div>
  )
}
export default ProductCard

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" },
    ],
  },
];

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" },
    ],
  },
];

const Checkboxes = ({data}) => {
  return (
    <div>
    </div>
  );
};

export default function NestedCheckbox() {

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

App.js

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

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.

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 }) => {

  return (
    <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);

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

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.

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 from "react";
import './styles.css'

function DataTable({data}) {
    return (
        <div>
            {/* Implement table with pagination here */}
        </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} />;
}

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>

      <div style={{ marginBottom: "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>

      <table border="1" cellPadding="10" style={{ borderCollapse: "collapse" }}>
        <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={{ marginTop: "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>
  );
}

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.

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; // Total number of OTP input boxes
  const [otp, setOTP] = useState(Array(length).fill("")); // State to store each digit
  const inputsRef = useRef([]); // Array of input refs to control focus

 
  const focusInput = (index) => {
    // TODO: Focus the input element at the specified index
  };

  const handleChange = (e, index) => {
    // TODO: Implement value validation, state update, auto-focus, and OTP completion check
  };

 
  const handleKeyDown = (e, index) => {
    // TODO: Handle backspace behavior for navigation
  };

 
  const handlePaste = (e) => {
    // TODO: Extract numeric values from pasted string and update inputs accordingly
  };

  // Render the OTP input fields
  return (
    <div onPaste={handlePaste}>
      {otp.map((digit, index) => (
        <input
          key={index}
          ref={(el) => (inputsRef.current[index] = el)} // Save input ref for focus management
          type="text" // Use text input for better control over formatting
          maxLength="1" // Limit to 1 character per input
          inputMode="numeric" // Show numeric keyboard on mobile devices
          value={digit} // Controlled input tied to state
          onChange={(e) => handleChange(e, index)} // Handle typing
          onKeyDown={(e) => handleKeyDown(e, index)} // Handle backspace
          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} />;
}

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.

MatchpairGame.jsx

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

const initialEmojis = ['❤️','🍀','🌎','🍎','⚽️','🚗','⛵️','💎'];

const MatchPairGame = () => {
  const [cards, setCards] = useState([]); // Each card: { id, value, revealed, matched }
  const [firstCard, setFirstCard] = useState(null);
  const [secondCard, setSecondCard] = useState(null);
  const [moves, setMoves] = useState(0);
  const [won, setWon] = useState(false);

  useEffect(() => {
    // TODO: Shuffle the emoji list and initialize the cards array
  }, []);

  const handleClick = (card) => {
    // TODO: Handle card click logic here
  };

  const resetGame = () => {
    // TODO: Reset the game to initial state
  };

  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' : ''}`}
            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/>
}

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