Hard Flashcards
Hard level React coding assignments (7 cards)
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
- Navigation Bar
* Display a navigation bar with Home and Products links. - Home Page
* Display a simple welcome message: “Welcome to the Home Page” - 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. - 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
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;
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
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;
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
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/> }
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
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; }); }
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
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} />; }
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
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} />; }
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
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/> }