Todo List
Description
Build a Todo List component where users can:
* Add a new todo item.
* Mark a todo as completed.
* Delete a todo item.
* View the list of all todos.
Requirements
* The component should maintain a list of todos in its state.
* A todo has: id, text, and completed (boolean).
* An input box with placeholder “Enter todo” to type a new todo.
* A button labelled “Add” to add a todo.
* Each todo should display its text and a checkbox to toggle completion.
* Each todo should have a delete button labelled “Delete” to delete a todo.
* Completed todos should appear with a strikethrough style.
Constraints & Edge Cases
* Todo text should not be empty.
* Case-insensitive duplicate entries should be allowed.
* Deleting an item should not affect the remaining list.
* All operations should update the Ul immediately.
https://namastedev.com/practice/todo-list
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;
}TodoList.js
import React, { useState } from "react";
import "./styles.css";
function TodoList() {
const [todos, setTodos] = useState([]);
const [inputValue, setInputValue] = useState("");
const handleAdd = () => {
const trimmed = inputValue.trim();
if (trimmed === "") return;
const newTodo = {
id: Date.now(),
text: trimmed,
completed: false,
};
setTodos((prevTodos) => [...prevTodos, newTodo]);
setInputValue("");
};
const toggleComplete = (id) => {
setTodos((prevTodos) =>
prevTodos.map((todo) =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
);
};
const deleteTodo = (id) => {
setTodos((prevTodos) => prevTodos.filter((todo) => todo.id !== id));
};
return (
<div style={{ padding: "20px", maxWidth: "400px", margin: "0 auto" }}>
<h1>Todo List</h1>
<div>
<input
type="text"
placeholder="Enter todo"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
style={{ padding: "8px", width: "70%", marginRight: "10px" }}
/>
<button onClick={handleAdd} style={{ padding: "8px 12px" }}>
Add
</button>
</div>
<ul style={{ listStyle: "none", padding: 0, marginTop: "20px" }}>
{todos.map((todo) => (
<li
key={todo.id}
style={{
display: "flex",
alignItems: "center",
marginBottom: "10px",
}}
>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleComplete(todo.id)}
style={{ marginRight: "10px" }}
/>
<span
style={{
textDecoration: todo.completed ? "line-through" : "none",
flex: 1,
}}
>
{todo.text}
</span>
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</li>
))}
</ul>
</div>
);
}
export default TodoList;App.js
import TodoList from "./TodoList";
export default function App() {
return <TodoList />;
}Tabs Component
Description
Implement a Tabs Component that allows users to switch * between different tab content sections. Each tab should be clickable, and clicking on a tab should update the displayed content accordingly.
Requirements
1. The component should accept a list of tabs as props.
2. Each tab should have a title and content.
3. The first tab should be selected by default.
4. Clicking on a tab should update the displayed content.
5. The component should be reusable and scalable.
Constraints
* Constraint 1: The tabs prop is an array of objects, each containing title and content.
* Constraint 2: Handle an empty tabs array gracefully by displaying “No tabs available”.
* Constraint 3: Handle cases where title is missing in a tab by displaying “Tab 1”, “Tab 2”, according to the tab number.
* Constraint 4: Handle cases where content is missing in a tab by displaying “No content available”.
https://namastedev.com/practice/tabs-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;
}Tabs.js
import React, { useState } from "react";
import "./styles.css";
function Tabs({ tabs }) {
const [activeIndex, setActiveIndex] = useState(0);
if (!tabs || tabs.length === 0) {
return <p>No tabs available</p>;
}
const getTitle = (tab, index) => tab.title || `Tab ${index + 1}`;
const getContent = (tab) => tab.content || "No content available";
return (
<div>
{/* Tab Titles */}
<div style={{ display: "flex", marginBottom: "10px" }}>
{tabs.map((tab, index) => (
<button
key={index}
onClick={() => setActiveIndex(index)}
style={{
padding: "10px 20px",
marginRight: "5px",
cursor: "pointer",
border: "1px solid #ccc",
backgroundColor: index === activeIndex ? "#eee" : "#fff",
fontWeight: index === activeIndex ? "bold" : "normal",
}}
>
{getTitle(tab, index)}
</button>
))}
</div>
{/* Active Tab Content */}
<div
style={{
padding: "15px",
border: "1px solid #ccc",
backgroundColor: "#f9f9f9",
}}
>
{getContent(tabs[activeIndex])}
</div>
</div>
);
}
export default Tabs;App.js
import Tabs from "./Tabs";
export default function App() {
const tabsData = [
{ title: "Tab 1", content: "This is the content of Tab 1" },
{ title: "Tab 2", content: "This is the content of Tab 2" },
{ title: "Tab 3", content: "This is the content of Tab 3" }
];
return <Tabs tabs={tabsData} />;
}Todo List II
Description
Build a Todo List component with timer functionality where users can:
* Add a new todo item.
* Start/Pause a timer for each todo.
* Reset a timer to zero.
* Delete a todo item.
* View the list of all todos with their timers.
Requirements
* The component should maintain a list of todos in its state.
* A todo has: id, title, time (in seconds), and isRunning (boolean).
* An input box with placeholder “Enter todo” to type a new todo.
* A button labeled “Add” to add a todo.
* Each todo item must be an < li> element with the attribute data-testid=”todo-item”
* Example of a correctly structured todo item:
<li data-testid="todo-item" className="todo-item">
<span className="todo-text">Example Todo</span>
<div className="timer">00:00</div>
<div className="button-group">
<button className="timer-button start">Start</button>
<button className="timer-button reset">Reset</button>
<button className="delete-button">Delete</button>
</div>
</li>Constraints & Edge Cases
* Todo text should not be empty.
* Empty todos should not be added to the list.
* Deleting an item should not affect the remaining list or their timers.
* Pausing a timer should stop it from incrementing until resumed.
* Resetting a timer should set it to 00:00 and stop it.
* All operations should update the UI immediately.
* Timers should continue running even when other todos are being manipulated.
Implementation Details
* Use React hooks for state management.
* Implement proper cleanup for timers to prevent memory leaks.
* Format time display in MM:SS format (e.g., “01:45” for 1 minute and 45 seconds).
* Use appropriate styling to distinguish between running and paused timers.
Expected Output
A functional todo list application where:
* Users can add new todos
* Each todo has its own independent timer
* Timers can be started, paused, and reset
* Todos can be deleted
* The Ul updates in real-time as timers increment
https://namastedev.com/practice/todo-list-ii
.App {
font-family: sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.container {
background-color: #f8f9fa;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.todo-container {
width: 100%;
}
h1 {
color: #333;
margin-bottom: 10px;
}
h2 {
color: #444;
margin-bottom: 20px;
}
.input-container {
display: flex;
margin-bottom: 20px;
}
.todo-input {
flex-grow: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}
.add-button {
background-color: #4a90e2;
color: white;
border: none;
padding: 10px 15px;
margin-left: 10px;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
}
.add-button:hover {
background-color: #3a80d2;
}
.todo-list {
list-style: none;
padding: 0;
}
.todo-item {
background-color: white;
border-radius: 4px;
padding: 15px;
margin-bottom: 10px;
display: flex;
flex-direction: column;
gap: 10px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.todo-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.todo-title {
font-weight: bold;
margin: 0;
font-size: 18px;
}
.todo-time {
color: #666;
font-size: 16px;
font-weight: bold;
margin: 0;
min-width: 60px;
text-align: right;
}
.todo-actions {
display: flex;
gap: 10px;
}
.timer-button,
.reset-button,
.delete-button {
padding: 8px 12px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
transition: background-color 0.2s;
}
.timer-button.start {
background-color: #4caf50;
color: white;
}
.timer-button.start:hover {
background-color: #3d9c40;
}
.timer-button.pause {
background-color: #ff9800;
color: white;
}
.timer-button.pause:hover {
background-color: #e68900;
}
.reset-button {
background-color: #f44336;
color: white;
}
.reset-button:hover {
background-color: #e53935;
}
.delete-button {
background-color: #757575;
color: white;
}
.delete-button:hover {
background-color: #616161;
}
@media (max-width: 600px) {
.todo-content {
flex-direction: column;
align-items: flex-start;
}
.todo-time {
text-align: left;
margin-top: 5px;
}
.todo-actions {
flex-wrap: wrap;
}
}
App.js
~~~
import React from “react”;
import TodoWithTimeout from “./TodoWithTimeout”;
import “./styles.css”;
export default function App() {
return (
<div className="App">
<h2>Todo List with Timer</h2>
<p>
Build a todo list where each task has its own timer that can be started,
paused, and reset.
</p>
<div className="container">
<TodoWithTimeout></TodoWithTimeout>
{/* <Template></Template> */}
</div>
</div>
);
}
TodoWithTimeout.js
import React, { useState, useEffect, useRef } from “react”;
import “./styles.css”;
function formatTime(seconds) {
const mins = String(Math.floor(seconds / 60)).padStart(2, “0”);
const secs = String(seconds % 60).padStart(2, “0”);
return ${mins}:${secs};
}
function TodoWithTimeout() {
const [todos, setTodos] = useState([]);
const [input, setInput] = useState(“”);
const timers = useRef({}); // Holds timer intervals keyed by todo id
useEffect(() => {
return () => {
// Cleanup intervals on unmount
Object.values(timers.current).forEach(clearInterval);
};
}, []);
const handleAddTodo = () => {
const trimmed = input.trim();
if (!trimmed) return;
const newTodo = {
id: Date.now(),
text: trimmed,
time: 0,
running: false,
};
setTodos((prev) => [...prev, newTodo]);
setInput(""); };const toggleTimer = (id) => {
setTodos((prevTodos) =>
prevTodos.map((todo) => {
if (todo.id === id) {
if (todo.running) {
clearInterval(timers.current[id]);
delete timers.current[id];
} else {
timers.current[id] = setInterval(() => {
setTodos((prev) =>
prev.map((t) =>
t.id === id ? { …t, time: t.time + 1 } : t
)
);
}, 1000);
}
return { …todo, running: !todo.running };
}
return todo;
})
);
};
const resetTimer = (id) => {
clearInterval(timers.current[id]);
delete timers.current[id];
setTodos((prev) =>
prev.map((todo) =>
todo.id === id ? { …todo, time: 0, running: false } : todo
)
);
};
const deleteTodo = (id) => {
clearInterval(timers.current[id]);
delete timers.current[id];
setTodos((prev) => prev.filter((todo) => todo.id !== id));
};
return (
<div className="todo-container">
<h2>Todo with Timer</h2>
<div className="input-container">
<input
type=”text”
className=”todo-input”
data-testid=”todo-input”
placeholder=”Enter todo”
value={input}
onChange={(e) => setInput(e.target.value)}
/>
<button
className=”add-button”
data-testid=”add-button”
onClick={handleAddTodo}
>
Add
</button>
</div>
<ul className="todo-list">
{todos.map((todo) => (
<li key={todo.id} data-testid=”todo-item” className=”todo-item”>
<div className="todo-content">
<p className="todo-title">{todo.text}</p>
<p className="todo-time">{formatTime(todo.time)}</p>
</div>
<div className="todo-actions">
<button
className={timer-button ${todo.running ? "pause" : "start"}}
onClick={() => toggleTimer(todo.id)}
>
{todo.running ? “Pause” : “Start”}
</button>
<button
className=”reset-button”
onClick={() => resetTimer(todo.id)}
>
Reset
</button>
<button
className=”delete-button”
onClick={() => deleteTodo(todo.id)}
>
Delete
</button>
</div>
</li>
))}
</ul>
</div>
);
}
export default TodoWithTimeout;
~~~
Image Gallery
Description
You are tasked with creating a dynamic Image Gallery
Application using React. The application should allow users to view, add, and delete images. Each image is represented bỳ a URL. Users can input a URL to add a new image, click an image to view a larger version, and remove any image they wish.
Requirements
Component Structure
* Use React Functional Components to build the application.
* Structure the application with a main ImageGallery component inside an App component.
Ul Behavior
* Display images in a responsive grid layout.
* Users should be able to:
Add a new image by entering a URL and clicking a button.
Delete an image from the gallery.
Click on any image to open it in a larger view.
Expected Output
* A heading: “Image Gallery”.
* An input field with the placeholder text “Enter image
URL” and a button labeled “Add Image” for adding image URLs.
* A responsive grid layout displaying all added images.
* A delete button on each image labelled “Delete” for deleting image.
* A modal-style popup must display a larger version of the image when clicked, and the modal ‹div> must have id=”modal” so it can be consistently and accurately targeted in tests.
* The large image view should close when the user clicks outside the image.
https://namastedev.com/practice/image-gallery
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;
}ImageGallery.jsx
import React, { useState } from 'react';
import './styles.css';
const ImageGallery = () => {
const [images, setImages] = useState([]);
const [input, setInput] = useState('');
const [selectedImage, setSelectedImage] = useState(null);
const handleAddImage = () => {
const trimmed = input.trim();
if (trimmed) {
setImages((prev) => [...prev, trimmed]);
setInput('');
}
};
const handleDeleteImage = (index) => {
setImages((prev) => prev.filter((_, i) => i !== index));
};
const handleClickOutside = (e) => {
if (e.target.id === 'modal') {
setSelectedImage(null);
}
};
return (
<div>
<h1>Image Gallery</h1>
{/* Input */}
<div style={{ marginBottom: '20px' }}>
<input
type="text"
placeholder="Enter image URL"
value={input}
onChange={(e) => setInput(e.target.value)}
style={{ padding: '10px', width: '300px', marginRight: '10px' }}
/>
<button onClick={handleAddImage}>Add Image</button>
</div>
{/* Image Grid */}
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))',
gap: '15px'
}}
>
{images.map((url, index) => (
<div key={index} style={{ position: 'relative' }}>
<img
src={url}
alt={`Gallery ${index}`}
style={{
width: '100%',
height: '150px',
objectFit: 'cover',
cursor: 'pointer',
borderRadius: '8px'
}}
onClick={() => setSelectedImage(url)}
/>
<button
onClick={() => handleDeleteImage(index)}
style={{
position: 'absolute',
top: '5px',
right: '5px',
backgroundColor: '#ff4d4f',
color: 'white',
border: 'none',
padding: '5px 10px',
borderRadius: '4px',
cursor: 'pointer'
}}
>
Delete
</button>
</div>
))}
</div>
{/* Modal */}
{selectedImage && (
<div
id="modal"
onClick={handleClickOutside}
style={{
position: 'fixed',
top: 0,
left: 0,
width: '100vw',
height: '100vh',
backgroundColor: 'rgba(0,0,0,0.7)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 999
}}
>
<img
src={selectedImage}
alt="Enlarged"
style={{
maxWidth: '90%',
maxHeight: '90%',
borderRadius: '8px',
boxShadow: '0 0 10px rgba(255,255,255,0.5)'
}}
/>
</div>
)}
</div>
);
};
export default ImageGallery;App.js
import ImageGallery from './ImageGallery'
export default function App() {
return <ImageGallery/>
}Stopwatch
Description
Build a simple stopwatch with Start, Stop, and Reset functionality. The stopwatch should display elapsed time in seconds and update every second while running.
Requirements
* Display a timer starting at 0.
* Include three buttons: Start, Stop, and Reset.
* On clicking Start, the timer should begin incrementing every second.
* Stop should pause the timer.
* Reset should stop the timer and reset it back to 0.
* Ensure the timer doesn’t increment multiple times if
Start is clicked repeatedly.
Constraints & Edge Cases
* Timer should not increment if already running and Start is clicked again.
* After Reset, the timer should show 0 and stop ticking.
* Stop should not reset the time.
* Memory leaks must be avoided when using intervals.
https://namastedev.com/practice/stopwatch
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;
}Stopwatch.js
import React, { useState, useRef, useEffect } from "react";
import "./styles.css";
function Stopwatch() {
const [seconds, setSeconds] = useState(0);
const [isRunning, setIsRunning] = useState(false);
const intervalRef = useRef(null);
const startTimer = () => {
if (!isRunning) {
setIsRunning(true);
intervalRef.current = setInterval(() => {
setSeconds(prev => prev + 1);
}, 1000);
}
};
const stopTimer = () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
setIsRunning(false);
};
const resetTimer = () => {
stopTimer();
setSeconds(0);
};
// Cleanup interval on component unmount
useEffect(() => {
return () => {
clearInterval(intervalRef.current);
};
}, []);
return (
<div style={{ textAlign: "center", marginTop: "20px" }}>
<h1>Time: {seconds}s</h1>
<button onClick={startTimer}>Start</button>
<button onClick={stopTimer}>Stop</button>
<button onClick={resetTimer}>Reset</button>
</div>
);
}
export default Stopwatch;App.js
import Stopwatch from "./Stopwatch";
export default function App() {
return <Stopwatch />;
}Drag and Drop
Description
Create a drag-and-drop fruits organizer using React that allows users to move fruits between “Available Fruits” and
“Dropped Fruits” columns, reorder them, and reset to the initial state.
Requirements
* Render two droppable columns:
Left column: Available Fruits
Right column: Dropped Fruits
* Display a reset button labeled “Reset Lists” to restore the initial state.
* Fruit Items Display:
Each fruit is a draggable item with a unique name.
Initially, all fruits appear in the “Available Fruits” column.
Empty messages display when a column has no items:
“Drop fruits here” for the Dropped column.
“No fruits here” for the Available column.
* Drag & Drop Behavior:
Dragging a fruit from one column to another moves it to the target column.
Dropping a fruit on another fruit within the same column reorders the items.
Dropping in an empty column area appends the fruit at the end.
Visual feedback during drag helps indicate valid drop targets.
Edge Cases & Constraints:
* Reordering within the same column keeps all items intact.
* Dragging to a different column moves the item entirely.
* Reset button:
Clears the dropped fruits.
Restores all original fruits back to the Available column.
Testing Requirements
1. Data Test IDs (required for testing):
* data-testid=”available-column” - Column for available fruits
* data-testid=”dropped-column” - Column for dropped fruits
* data-testid=”item-{id}” - Individual fruit items, e.g., item-1, item-2
* data-testid=”reset-button” - Reset button to reset all items
* data-testid=”available-empty” - Message when available list is empty
* data-testid=”dropped-empty” - Message when dropped list is empty
https://namastedev.com/practice/drag-and-drop
styles.css
body {
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
background: #f4f6f8;
margin: 0;
padding: 20px;
color: #2e3a59;
}
.app-wrapper {
max-width: 900px;
margin: 0 auto;
}
header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 28px;
border-bottom: 1px solid #d1d9e6;
padding-bottom: 10px;
}
h1 {
font-weight: 700;
font-size: 1.9rem;
color: #1f2937;
margin: 0;
}
.reset-btn {
background-color: #374151;
color: #f3f4f6;
border: none;
padding: 10px 20px;
border-radius: 8px;
font-weight: 600;
font-size: 1rem;
cursor: pointer;
box-shadow: 0 3px 6px rgb(55 65 81 / 0.3);
transition: background-color 0.3s ease, box-shadow 0.3s ease;
user-select: none;
}
.reset-btn:hover {
background-color: #1f2937;
box-shadow: 0 6px 12px rgb(31 41 55 / 0.5);
}
.container {
display: flex;
justify-content: space-between;
gap: 24px;
}
.column {
background: #ffffff;
padding: 24px 28px;
width: 48%;
min-height: 360px;
border-radius: 12px;
box-shadow: 0 10px 20px rgb(0 0 0 / 0.07);
border: 1px solid #cbd5e1;
transition: border-color 0.3s ease;
}
.column.drop-zone {
border: 2px dashed #4b5563;
}
.column:hover {
border-color: #4b5563;
}
h2 {
margin-top: 0;
font-weight: 600;
font-size: 1.5rem;
color: #181c23;
border-bottom: 1px solid #e5e7eb;
padding-bottom: 8px;
}
.item {
background: #c8e0f9;
margin: 14px 0;
padding: 14px 20px;
border-radius: 10px;
cursor: grab;
user-select: none;
font-size: 1.15rem;
color: #374151;
box-shadow: 0 3px 8px rgb(100 116 139 / 0.1);
transition: background-color 0.25s ease, box-shadow 0.25s ease;
border: 1px solid transparent;
}
.item:active {
cursor: grabbing;
background-color: #e5e7eb;
box-shadow: 0 8px 16px rgb(100 116 139 / 0.2);
border-color: #9ca3af;
}
.empty {
color: #6b7280;
font-style: italic;
margin-top: 36px;
font-size: 1rem;
text-align: center;
}App.js
import DragDrop from "./DragDrop";
export default function App() {
return <DragDrop />;
}DragDrop.js
import React, { useState } from "react";
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
import "./styles.css";
const initialFruits = [
{ id: "1", name: "Apple" },
{ id: "2", name: "Banana" },
{ id: "3", name: "Cherry" },
{ id: "4", name: "Date" },
{ id: "5", name: "Elderberry" }
];
const DragDrop = () => {
const [available, setAvailable] = useState(initialFruits);
const [dropped, setDropped] = useState([]);
const handleDragEnd = (result) => {
const { source, destination } = result;
if (!destination) return;
const sourceList = source.droppableId === "available" ? available : dropped;
const destList = destination.droppableId === "available" ? available : dropped;
const setSourceList = source.droppableId === "available" ? setAvailable : setDropped;
const setDestList = destination.droppableId === "available" ? setAvailable : setDropped;
const [movedItem] = sourceList.splice(source.index, 1);
if (source.droppableId === destination.droppableId) {
// Reordering within the same column
const updatedList = Array.from(sourceList);
updatedList.splice(destination.index, 0, movedItem);
setSourceList(updatedList);
} else {
// Moving to another column
const updatedSource = Array.from(sourceList);
const updatedDest = Array.from(destList);
updatedDest.splice(destination.index, 0, movedItem);
setSourceList(updatedSource);
setDestList(updatedDest);
}
};
const handleReset = () => {
setAvailable(initialFruits);
setDropped([]);
};
return (
<div className="app-wrapper">
<header>
<h1>Drag & Drop Fruits</h1>
<button
className="reset-btn"
onClick={handleReset}
data-testid="reset-button"
>
Reset Lists
</button>
</header>
<DragDropContext onDragEnd={handleDragEnd}>
<div className="container">
{/* Available Fruits Column */}
<Droppable droppableId="available">
{(provided, snapshot) => (
<div
className={`column ${snapshot.isDraggingOver ? "drop-zone" : ""}`}
ref={provided.innerRef}
{...provided.droppableProps}
data-testid="available-column"
>
<h2>Available Fruits</h2>
{available.length === 0 && (
<p className="empty" data-testid="available-empty">
No fruits here
</p>
)}
{available.map((fruit, index) => (
<Draggable key={fruit.id} draggableId={fruit.id} index={index}>
{(provided) => (
<div
className="item"
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
data-testid={`item-${fruit.id}`}
>
{fruit.name}
</div>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</Droppable>
{/* Dropped Fruits Column */}
<Droppable droppableId="dropped">
{(provided, snapshot) => (
<div
className={`column ${snapshot.isDraggingOver ? "drop-zone" : ""}`}
ref={provided.innerRef}
{...provided.droppableProps}
data-testid="dropped-column"
>
<h2>Dropped Fruits</h2>
{dropped.length === 0 && (
<p className="empty" data-testid="dropped-empty">
Drop fruits here
</p>
)}
{dropped.map((fruit, index) => (
<Draggable key={fruit.id} draggableId={fruit.id} index={index}>
{(provided) => (
<div
className="item"
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
data-testid={`item-${fruit.id}`}
>
{fruit.name}
</div>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</Droppable>
</div>
</DragDropContext>
</div>
);
};
export default DragDrop;Animated Loading Skeleton
Description
Create a React component named LoadingSkeleton that® simulates loading a user profile card. While loading, show an animated skeleton (grey boxes and lines with a shimmer effect). After 2 seconds, replace it with real profile content: an image, name, and bio.
Requirements
1. Create a LoadingSkeleton component.
2. Show a skeleton Ul (name line, bio line) while loading.
3. Use a shimmer animation for a smooth user experience.
4. After 2 seconds, display:
A user image (placeholder)
User name (e.g., John Doe)
Bio (e.g., Full-stack developer at XYZ company)
https://namastedev.com/practice/animated-loading-skeleton
styles.css
.card {
width: 300px;
margin: 40px auto;
padding: 20px;
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
text-align: center;
font-family: sans-serif;
}
.skeleton-wrapper {
display: flex;
flex-direction: column;
align-items: center;
}
.line {
height: 20px;
background-color: #e2e2e2;
border-radius: 4px;
margin: 10px 0;
}
.name {
width: 60%;
}
.bio {
width: 80%;
}
.skeleton {
position: relative;
overflow: hidden;
background-color: #ddd;
}
.skeleton::after {
content: "";
position: absolute;
top: 0;
left: -100%;
height: 100%;
width: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.6), transparent);
animation: shimmer 1.5s infinite;
}
.image-skeleton {
width: 100px;
height: 100px;
border-radius: 50%;
margin-bottom: 20px;
}
@keyframes shimmer {
100% {
transform: translateX(100%);
}
}
.profile-img {
width: 100px;
height: 100px;
border-radius: 50%;
margin-bottom: 20px;
}LoadingSkeleton.js
import React, { useState, useEffect } from "react";
import "./styles.css";
function LoadingSkeleton() {
const [loading, setLoading] = useState(true);
useEffect(() => {
const timer = setTimeout(() => setLoading(false), 2000);
return () => clearTimeout(timer);
}, []);
return (
<div className="card">
{loading ? (
<div className="skeleton-wrapper">
<div className="skeleton image-skeleton"></div>
<div className="skeleton line name"></div>
<div className="skeleton line bio"></div>
</div>
) : (
<div className="content">
<img
src="https://via.placeholder.com/150"
alt="John Doe"
className="profile-img"
/>
<h2>John Doe</h2>
<p>Full-stack developer at XYZ company</p>
</div>
)}
</div>
);
}
export default LoadingSkeleton;App.js
import LoadingSkeleton from "./LoadingSkeleton";
export default function App() {
return <LoadingSkeleton />;
}Transfer List
Description
Build a simple Transfer List component in React that allows users to move items between two lists: “Available Items” and
“Selected Items”.
Requirements
* Display two lists: Available Items and Selected Items.
* Each item should be represented by a checkbox.
* Provide buttons to move selected items between the lists.
* Only use these items “Item A”, “Item B”, “Item C”
* Handle selection/deselection of items.
* Lists should update accordingly after every move.
Constraints & Edge Cases
* Items must not duplicate when moved.
* Moving an item should remove it from its current list.
* Handle empty selection without error.
* Initial state can be hardcoded for simplicity.
https://namastedev.com/practice/transfer-list
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;
}TransferList.js
import React, { useState } from "react";
import "./styles.css";
export default function TransferList() {
const initialItems = ["Item A", "Item B", "Item C"];
const [available, setAvailable] = useState(initialItems);
const [selected, setSelected] = useState([]);
const [checkedAvailable, setCheckedAvailable] = useState([]);
const [checkedSelected, setCheckedSelected] = useState([]);
const handleCheck = (item, list, setter) => {
setter((prev) =>
prev.includes(item)
? prev.filter((i) => i !== item)
: [...prev, item]
);
};
const moveToSelected = () => {
const newAvailable = available.filter((item) => !checkedAvailable.includes(item));
const newSelected = [...selected, ...checkedAvailable];
setAvailable(newAvailable);
setSelected(newSelected);
setCheckedAvailable([]);
};
const moveToAvailable = () => {
const newSelected = selected.filter((item) => !checkedSelected.includes(item));
const newAvailable = [...available, ...checkedSelected];
setSelected(newSelected);
setAvailable(newAvailable);
setCheckedSelected([]);
};
return (
<div style={{ display: "flex", justifyContent: "center", gap: "50px", paddingTop: "30px" }}>
<div>
<h1>Available Items</h1>
{available.length === 0 ? <p>No items</p> : null}
{available.map((item) => (
<div key={item}>
<label>
<input
type="checkbox"
checked={checkedAvailable.includes(item)}
onChange={() => handleCheck(item, available, setCheckedAvailable)}
/>
{item}
</label>
</div>
))}
<button onClick={moveToSelected} disabled={checkedAvailable.length === 0}>
Move to Selected →
</button>
</div>
<div>
<h1>Selected Items</h1>
{selected.length === 0 ? <p>No items</p> : null}
{selected.map((item) => (
<div key={item}>
<label>
<input
type="checkbox"
checked={checkedSelected.includes(item)}
onChange={() => handleCheck(item, selected, setCheckedSelected)}
/>
{item}
</label>
</div>
))}
<button onClick={moveToAvailable} disabled={checkedSelected.length === 0}>
← Move to Available
</button>
</div>
</div>
);
}App.js
import TransferList from "./TransferList";
export default function App() {
return <TransferList />;
}Modal In React
Description
Create a simple, functional modal popup in React with clear open/close behavior. This component demonstrates Ul state management with useState, conditional rendering, and handling user interactions such as clicking outside the modal or on the close button.
Component Details
You need to implement a Modal component that:
1. Displays a button labeled “Open Modal”.
2. Opens the modal when the “Open Modal” button is
* clicked.
3. Displays modal content with the following:
A heading: “Modal Header”
A paragraph: “This is the modal body”
A “Close” button inside the modal
4. Closes the modal when:
The “Close” button is clicked
The user clicks outside the modal (on the backdrop)
5. Does not close the modal when clicking inside the modal content
6. The outermost div that wraps the modal (when visible) must have data-testid=”modal-backdrop” for testing.
https://namastedev.com/practice/modal-in-react
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;
}Modal.js
import React, { useState } from "react";
function Modal() {
const [isOpen, setIsOpen] = useState(false);
const handleOpen = () => setIsOpen(true);
const handleClose = () => setIsOpen(false);
const handleBackdropClick = (e) => {
// Only close if the user clicks directly on the backdrop
if (e.target.dataset.testid === "modal-backdrop") {
handleClose();
}
};
return (
<div style={{ textAlign: "center", padding: "50px", height: "100vh" }}>
<h1>Modal Popup</h1>
<button
style={{ padding: "10px", cursor: "pointer" }}
onClick={handleOpen}
>
Open Modal
</button>
{isOpen && (
<div
data-testid="modal-backdrop"
onClick={handleBackdropClick}
style={{
position: "fixed",
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: "rgba(0, 0, 0, 0.4)",
display: "flex",
justifyContent: "center",
alignItems: "center",
zIndex: 1000,
}}
>
<div
onClick={(e) => e.stopPropagation()}
style={{
background: "white",
padding: "30px",
borderRadius: "8px",
minWidth: "300px",
boxShadow: "0 4px 12px rgba(0,0,0,0.2)",
textAlign: "left",
}}
>
<h2>Modal Header</h2>
<p>This is the modal body</p>
<button onClick={handleClose} style={{ marginTop: "20px" }}>
Close
</button>
</div>
</div>
)}
</div>
);
}
export default Modal;App.js
import React, { useState } from "react";
import Modal from "./Modal";
import './styles.css'
export default function App() {
return (
<Modal/>
);
}List Sorter
Description
The ListSorter component allows users to view and sort a predefined list of fruits by three different criteria: alphabetically A-Z, reverse alphabetically Z-A, and by name length (shortest to longest). The component starts by displaying the fruits in their original, hardcoded order, and only updates when the user chooses a sort option from a dropdown.
To maintain correctness, the original input list must remain consistent across usage and tests - this list is:["Banana", "Apple", "Cherry", "Mango", "Blueberry”]
This list is used to check the initial state of the component before any sorting is applied. It ensures test reliability and aligns with expected behaviors such as stable sorting and unaltered default rendering. The component leverages React’s useState to manage both the list and the selected sort type, updating the list based on the selected sort criteria while preserving the original list structure through immutability.
Requirements
* Display a list of fruit names.
* Provide a dropdown to select the sort type with three options:
“A - Z (Alphabetical)” - sort the list alphabetically from A to Z.
“Z - A (Reverse Alphabetical) - sort the list alphabetically from Z to A.
“Length (Shortest First)” - sort the list by the length of the fruit names from shortest to longest.
* When the user selects a sort option, the list updates immediately to reflect the sorting order.
* The component should maintain the current sorting state and update the list accordingly.
* The dropdown should use the following values for selection, which must be tested using:
Corresponding dropdown option values:
* A - Z (Alphabetical) - “az”
* Z - A (Reverse Alphabetical) - “za”
* Length (Shortest First) - “length”
Edge Cases & Constraints
* The initial list order should be preserved until the user selects a sort option.
* Sorting should be stable and accurate for all fruit names.
* Sorting by length should order fruits by their string length in ascending order.
* The list should update on every change of the dropdown value.
* The component should handle cases where multiple fruits might have the same length or alphabetical order.
Example Inputs & Outputs
// Initial List ["Banana", "Apple", "Cherry", "Mango", "Blueberry", "Kiwi", "Pineapple", "Fig"] // After selecting "A - Z (Alphabetical)" ["Apple", "Banana", "Blueberry", "Cherry", "Fig", "Kiwi", "Mango", "Pineapple"] // After selecting "Z - A (Reverse Alphabetical)" ["Pineapple", "Mango", "Kiwi", "Fig", "Cherry", "Blueberry", "Banana", "Apple"] // After selecting "Length (Shortest First)" ["Fig", "Kiwi", "Apple", "Mango", "Banana", "Cherry", "Blueberry", "Pineapple"]
Testing Requirements
* Verify that the component renders the heading, dropdown, and all list items correctly.
* Verify that the initial list order is exactly the original list.
* Test sorting for each sort type:
Alphabetical A-Z
Alphabetical Z-A
Length (Shortest First)
* Verify that the dropdown reflects the current selected sort option after user interaction.
Data Test IDs (required for testing)
* data-testid=”container” - main component container div.
* data-testid=”heading” - heading element with the component title.
* data-testid=”sort-dropdown” - select dropdown element for sorting.
* data-testid=”list-item” - each < li> representing a fruit.
https://namastedev.com/practice/list-sorter
styles.css
div[data-testid="container"] {
max-width: 400px;
margin: 50px auto;
padding: 36px 32px;
font-family: "Poppins", "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
background-color: #ede7f6;
border-radius: 20px;
box-shadow: 0 15px 40px rgba(107, 91, 149, 0.25);
color: #4b3b72;
user-select: none;
}
h2[data-testid="heading"] {
font-size: 2.25rem;
margin-bottom: 32px;
font-weight: 700;
color: #6b5b95;
text-align: center;
letter-spacing: 1.5px;
text-transform: uppercase;
text-shadow: 0 1px 3px rgba(155, 89, 182, 0.2);
}
label[for="sort"] {
display: block;
margin-bottom: 16px;
font-weight: 600;
font-size: 1.1rem;
color: #6b5b95;
}
select[data-testid="sort-dropdown"] {
width: 100%;
padding: 14px 18px;
font-size: 1.1rem;
border-radius: 14px;
border: 2px solid #b8a9c9;
background-color: #ffffff;
color: #4b3b72;
cursor: pointer;
transition: border-color 0.35s ease, box-shadow 0.35s ease;
box-shadow: inset 0 3px 8px rgba(184, 169, 201, 0.3);
font-weight: 600;
}
select[data-testid="sort-dropdown"]:focus {
border-color: #9b59b6;
box-shadow: 0 0 14px rgba(155, 89, 182, 0.6);
outline: none;
}
ul[data-testid="list"] {
list-style: none;
padding-left: 0;
margin-top: 36px;
border-radius: 18px;
background-color: #ffffff;
box-shadow: 0 12px 40px rgba(107, 91, 149, 0.15);
border: 1.8px solid #b8a9c9;
user-select: text;
}
li[data-testid="list-item"] {
padding: 18px 28px;
border-bottom: 1.4px solid #b8a9c9;
font-size: 1.2rem;
color: #4b3b72;
font-weight: 600;
transition: background-color 0.3s ease, color 0.3s ease;
cursor: default;
}
li[data-testid="list-item"]:hover {
background-color: #59a2b6;
color: #ede7f6;
font-weight: 700;
transform: scale(1.03);
box-shadow: 0 6px 14px rgba(155, 89, 182, 0.4);
}
li[data-testid="list-item"]:last-child {
border-bottom: none;
}App.js
import React from "react";
import ListSorter from "./ListSorter";
import "./styles.css";
// Default list passed as prop
const defaultFruits = [
"Banana",
"Apple",
"Cherry",
"Mango",
"Blueberry",
"Kiwi",
"Pineapple",
"Fig",
];
export default function App() {
return <ListSorter initialList={defaultFruits} />;
}ListSorter.js
import React, { useState } from "react";
import "./styles.css";
export default function ListSorter({ initialList = [] }) {
const [sortType, setSortType] = useState(""); // No sort by default
const [displayList, setDisplayList] = useState([...initialList]);
const handleSortChange = (e) => {
const value = e.target.value;
setSortType(value);
let sorted = [...initialList]; // Always sort from the original list
if (value === "az") {
sorted.sort((a, b) => a.localeCompare(b));
} else if (value === "za") {
sorted.sort((a, b) => b.localeCompare(a));
} else if (value === "length") {
sorted.sort((a, b) => a.length - b.length);
}
setDisplayList(sorted);
};
return (
<div data-testid="container">
<div>
<h2 data-testid="heading">List Sorter</h2>
</div>
<label htmlFor="sort">Sort By:</label>
<select
id="sort"
data-testid="sort-dropdown"
value={sortType}
onChange={handleSortChange}
>
<option value="">-- Select --</option>
<option value="az">A - Z (Alphabetical)</option>
<option value="za">Z - A (Reverse Alphabetical)</option>
<option value="length">Length (Shortest First)</option>
</select>
<ul data-testid="list">
{displayList.map((fruit, index) => (
<li key={index} data-testid="list-item">
{fruit}
</li>
))}
</ul>
</div>
);
}Multiselect Dropdown
Description
Create a React component that allows users to select multiple options with checkboxes from a dropdown menu. The component must handle option selection, deselection, and allow resetting all selections. It should display the current selection count and the selected options and validate user input on submission.
Requirements
Functional Requirements
* The component must:
Display a label above the dropdown with the text
“Select Options:”.
Show a dropdown toggle button with placeholder text
“Choose Options” when no option is selected.
Show the count of selected options (e.g., “3 selected”’) when options are selected.
Include a dropdown icon that rotates on toggle open/close. Use lucide-icon “ChevronDown” Display exactly 10 options (eg. “Option 2, Option 7” ) with checkboxes inside the dropdown menu. The menu should be scrollable. It should be in a list format (<li></li>)
Allow selecting or deselecting multiple options by clicking each option.
Provide a “Reset Selection” button on top inside the dropdown menu to clear all selections.
Close the dropdown menu when clicking outside the component.
Show a submit button below the dropdown. The button is clicked after the menu is closed.
On submit:
If no option is selected, display an error message:
“Please select at least one option.”
If options are selected, display the selected options below the submit button: “Selected: Option 3, Option 5”
Edge cases and constraints
* Submitting with valid selections should not show any error.
* Clicking an option multiple times should toggle selection (select/deselect).
* Leading/trailing spaces in option labels (if any) must not affect selection logic.
* Dropdown button should display “Choose Options” if no options are selected.
* After valid submission, selected options should be clearly displayed in the result area.
* Clicking the Reset button must:
Clear all selected options
, Hide any error messages
Clear previous results
* Dropdown icon must rotate only when the menu is open.
Testing Requirements
Data Test IDs (Required for Testing)
* data-testid=”label”: Label text element.
* data-testid=”dropdown-button”: The dropdown toggle button.
* data-testid=”dropdown-icon”: Icon element indicating dropdown toggle state.
* data-testid=”dropdown-menu”: The container element for the dropdown options.
* data-testid={option-${option}}: Each option item container, e.g., option-Option 1.
* data-testid=”reset-button”: The Reset selections button.
* data-testid=”submit-button”: Submit button element.
* data-testid=”error-message”: Element showing validation error messages.
* data-testid=”selected-options”: Element
showing the list of selected options.
* classname= “rotate”: Used to check the rotation of the icon.
https://namastedev.com/practice/multiselect-dropdown
App.js
import MultiSelectDropdown from "./MultiSelectDropdown";
export default function App() {
return <MultiSelectDropdown />;
}MultiSelectDropdown.js
import React, { useState, useRef, useEffect } from "react";
import { ChevronDown } from "lucide-react";
import "./styles.css";
const OPTIONS = Array.from({ length: 10 }, (_, i) => `Option ${i + 1}`);
function MultiSelectDropdown() {
const [isOpen, setIsOpen] = useState(false);
const [selected, setSelected] = useState([]);
const [error, setError] = useState("");
const [submittedOptions, setSubmittedOptions] = useState([]);
const dropdownRef = useRef(null);
const toggleDropdown = () => {
setIsOpen((prev) => !prev);
};
const handleOptionToggle = (option) => {
setSelected((prev) =>
prev.includes(option)
? prev.filter((item) => item !== option)
: [...prev, option]
);
setError("");
setSubmittedOptions([]);
};
const handleReset = () => {
setSelected([]);
setError("");
setSubmittedOptions([]);
};
const handleSubmit = () => {
setIsOpen(false);
if (selected.length === 0) {
setError("Please select at least one option.");
setSubmittedOptions([]);
} else {
setError("");
setSubmittedOptions([...selected]);
}
};
const handleClickOutside = (e) => {
if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
setIsOpen(false);
}
};
useEffect(() => {
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, []);
return (
<div className="dropdown-container" ref={dropdownRef}>
<h2 className="dropdown-title">Multiselect Dropdown Menu</h2>
<label data-testid="label" className="dropdown-label">Select Options:</label>
<div className="dropdown-wrapper">
<button
className="dropdown-toggle"
onClick={toggleDropdown}
data-testid="dropdown-button"
>
<span
data-testid="dropdown-icon"
className={`dropdown-icon ${isOpen ? "rotate" : ""}`}
>
<ChevronDown />
</span>
<span className="dropdown-button-label">
{selected.length === 0
? "Choose Options"
: `${selected.length} selected`}
</span>
</button>
{isOpen && (
<ul className="dropdown-menu" data-testid="dropdown-menu">
<li
className="dropdown-reset"
onClick={handleReset}
data-testid="reset-button"
>
Reset Selection
</li>
{OPTIONS.map((option) => (
<li
key={option}
className="dropdown-option"
data-testid={`option-${option}`}
onClick={() => handleOptionToggle(option)}
>
<input
type="checkbox"
checked={selected.includes(option)}
onChange={() => {}}
/>
<span className="option-label">{option}</span>
</li>
))}
</ul>
)}
</div>
<button
className="submit-button"
onClick={handleSubmit}
data-testid="submit-button"
>
Submit
</button>
{error && (
<div className="error" data-testid="error-message">
{error}
</div>
)}
{submittedOptions.length > 0 && (
<div className="result-area" data-testid="selected-options">
Selected: {submittedOptions.join(", ")}
</div>
)}
</div>
);
}
export default MultiSelectDropdown;Progress Bar II
Description
You’re given a task to display a list of progress bars with different percentage values (10%, 20%, .*, 100%), Each progress bar should:
1. Animate to the given percentage after a delay of 1 second.
2. Visually fill the bar based on the percentage using a
CSS transform.
3. Display the numeric progress as text (e.g., “20%”).
4. Show the text color as:
Black if progress is less than 5%.
White otherwise.
5. Have a green background and smooth animation using CSS transitions.
You are provided with two components:
* App. js: Renders a list of ProgressBar components.
* ProgressBar. js: A single progress bar that updates after 1 second using useEffect.
Your Task:
1. The progress bar text updates from 0% to the correct percentage after 100 ms.
2. The visual fill (via transform: translateX(…))
corresponds to the progress value.
3. The background color is green.
4. Text color should be white > 5% else black.
5. The transition style is smooth (transition: 0.5s ease-in).
6. The update happens with a delay of 100ms.
7. It is mandatory for the ProgressBar to have role=”progressbar” so it can be properly identified and tested for accessibility and styling.
https://namastedev.com/practice/progress-bar-ii
styles.css
.App {
font-family: sans-serif;
text-align: center;
padding: 20px;
}
.outer {
width: 80%;
height: 30px;
margin: 20px auto;
border: 1px solid #ccc;
background-color: #eee;
border-radius: 6px;
overflow: hidden;
position: relative;
}
.inner {
height: 100%;
background-color: green;
display: flex;
align-items: center;
justify-content: right;
transition: transform 0.5s ease-in;
transform: translateX(-100%);
white-space: nowrap;
font-weight: bold;
font-size: 14px;
}App.js
import "./styles.css";
import ProgressBar from "./ProgressBar";
export default function App() {
const bars = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100];
return (
<div className="App">
<h1>Progress bar</h1>
{bars.map((value, index) => (
<ProgressBar key={index} progress={value} />
))}
</div>
);
}ProgressBar.js
import { useEffect, useState } from "react";
import "./styles.css";
const ProgressBar = ({ progress }) => {
const [currentProgress, setCurrentProgress] = useState(0);
useEffect(() => {
const timer = setTimeout(() => {
setCurrentProgress(progress);
}, 100);
return () => clearTimeout(timer);
}, [progress]);
const transformStyle = {
transform: `translateX(${currentProgress - 100}%)`,
color: currentProgress < 5 ? "black" : "white"
};
return (
<div className="outer">
<div
className="inner"
role="progressbar"
style={transformStyle}
>
{currentProgress}%
</div>
</div>
);
};
export default ProgressBar;Reusable Toast
Description
In this problem, you need to implement a “toast” notification feature that provides brief feedback to users in the form of a popup. The toast should:
* Accept a message (string)
* Accept a type (success, error, or info)
* Accept a duration (milliseconds)
Toasts should:
* Appear at the top or bottom of the screen
* Automatically disappear after the specified time
* Allow multiple toasts to be displayed simultaneously
* Show different styles depending on the type (color-coded: green for success, red for error, blue for info)
Toast Container:
* Show Success - On clicking this button a success toast appears.
* Show Error - On clicking this button an error toast appears.
* Show Info - On clicking this button an info toast appears.
Example Inputs & Outputs
Input: addToast("Data saved successfully!", "success", 3000)
Output: Toast with green background appears for 3 seconds
Input: addToast("Error saving data!", "error", 5000)
Output: Toast with red background appears for 5 seconds
Input: addToast("Information loaded", "info", 4000)
Output: Toast with blue background appears for 4 secondsConstraints & Edge Cases
* Each toast should auto-dismiss after its specified duration.
* Support showing multiple toasts at once.
* Each toast should be uniquely identifiable for removal.
* The component must support easy customization of message, duration, and type.
* Ensure toasts don’t overlap visually in a confusing way.
* Make sure to use inline css.
https://namastedev.com/practice/reusable-toast
Toast.jsx
import React, { useEffect } from 'react';
const Toast = ({ id, message, type, duration, onRemove }) => {
useEffect(() => {
const timer = setTimeout(() => onRemove(id), duration);
return () => clearTimeout(timer);
}, [id, duration, onRemove]);
const backgroundColors = {
success: '#4CAF50',
error: '#F44336',
info: '#2196F3',
};
const toastStyle = {
padding: '12px 20px',
marginBottom: '10px',
color: 'white',
borderRadius: '6px',
backgroundColor: backgroundColors[type] || '#333',
minWidth: '200px',
boxShadow: '0 2px 8px rgba(0,0,0,0.2)',
fontSize: '16px',
};
return <div style={toastStyle}>{message}</div>;
};
export default Toast;ToastContainer.jsx
import React, { useState } from 'react';
import Toast from './Toast';
let toastId = 0;
const ToastContainer = () => {
const [toasts, setToasts] = useState([]);
const addToast = (message, type, duration) => {
const id = toastId++;
const newToast = { id, message, type, duration };
setToasts((prev) => [...prev, newToast]);
};
const removeToast = (id) => {
setToasts((prev) => prev.filter((toast) => toast.id !== id));
};
const containerStyle = {
position: 'fixed',
top: '20px',
right: '20px',
zIndex: 1000,
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-end',
};
const buttonContainerStyle = {
display: 'flex',
gap: '10px',
marginBottom: '20px',
};
const buttonStyle = {
padding: '10px 16px',
fontSize: '14px',
fontWeight: 'bold',
borderRadius: '6px',
border: 'none',
cursor: 'pointer',
color: 'white',
};
return (
<div>
<div style={buttonContainerStyle}>
<button
style={{ ...buttonStyle, backgroundColor: '#4CAF50' }}
onClick={() => addToast('Data saved successfully!', 'success', 3000)}
>
Show Success
</button>
<button
style={{ ...buttonStyle, backgroundColor: '#F44336' }}
onClick={() => addToast('Error saving data!', 'error', 5000)}
>
Show Error
</button>
<button
style={{ ...buttonStyle, backgroundColor: '#2196F3' }}
onClick={() => addToast('Information loaded', 'info', 4000)}
>
Show Info
</button>
</div>
<div style={containerStyle}>
{toasts.map((toast) => (
<Toast key={toast.id} {...toast} onRemove={removeToast} />
))}
</div>
</div>
);
};
export default ToastContainer;App.js
import React from 'react';
import ToastContainer from './ToastContainer';
const App = () => {
return (
<div>
<ToastContainer />
</div>
);
};
export default App;BMI Calculator
**Description **
Create a BMI (Body Mass Index) Calculator in React. The application should allow users to enter their weight in kilograms and height in centimeters, and on clicking a button, it should compute the BMI and display the corresponding category.
Requirements
Input Fields
* Weight (in kg) with placeholder “Weight (kg)”
* Height (in cm) with placeholder “Height (cm)”
On Clicking a button with label “Calculate BMI”
* Use the formula:
BMI = weight / ((height / 100) ^ 2)
* Round the BMI to 1 decimal place
* Display:
The BMI value - The BMI value with the text: “Your BMI:
X”
The BMI category - The BMI category with the text:
“Category: Y”
Where X is the calculated BMI and Y is the corresponding category (like Normal, Overweight, etc.).
BMI Categories
* Underweight: BMI < 18.5
* Normal: 18.5 ≤ BMI < 24.9
* Overweight: 25 ≤ BMI < 29.9
* Obese: BMI ≥ 30
Additional Feature
* Add a “Reset” button with label Reset to clear both inputs and the result.
Constraints & Edge Cases
* Weight and height must be positive numbers
* Input fields must not be empty before calculation
* Result should be rounded to 1 decimal place
* Reset must clear all fields and result
* BMI result must not be shown before clicking
“Calculate BMI”
https://namastedev.com/practice/bmi-calculator
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;
}BMICalculator.js
import React, { useState } from "react";
import './styles.css';
function BMICalculator() {
const [weight, setWeight] = useState('');
const [height, setHeight] = useState('');
const [bmi, setBMI] = useState(null);
const [category, setCategory] = useState('');
const calculateBMI = () => {
const w = parseFloat(weight);
const h = parseFloat(height);
if (!w || !h || w <= 0 || h <= 0) {
alert("Please enter valid positive numbers for weight and height.");
return;
}
const bmiValue = w / ((h / 100) ** 2);
const roundedBMI = Math.round(bmiValue * 10) / 10;
setBMI(roundedBMI);
setCategory(getCategory(roundedBMI));
};
const getCategory = (bmi) => {
if (bmi < 18.5) return "Underweight";
else if (bmi < 24.9) return "Normal";
else if (bmi < 29.9) return "Overweight";
else return "Obese";
};
const reset = () => {
setWeight('');
setHeight('');
setBMI(null);
setCategory('');
};
return (
<div style={{ padding: '20px', maxWidth: '400px', margin: '0 auto' }}>
<h2>BMI Calculator</h2>
<div style={{ marginBottom: '10px' }}>
<input
type="number"
value={weight}
onChange={(e) => setWeight(e.target.value)}
placeholder="Weight (kg)"
style={{ padding: '8px', width: '100%' }}
/>
</div>
<div style={{ marginBottom: '10px' }}>
<input
type="number"
value={height}
onChange={(e) => setHeight(e.target.value)}
placeholder="Height (cm)"
style={{ padding: '8px', width: '100%' }}
/>
</div>
<div style={{ marginBottom: '10px', display: 'flex', gap: '10px' }}>
<button onClick={calculateBMI} style={{ padding: '8px 16px' }}>Calculate BMI</button>
<button onClick={reset} style={{ padding: '8px 16px' }}>Reset</button>
</div>
{bmi !== null && (
<div style={{ marginTop: '15px' }}>
<p><strong>Your BMI:</strong> {bmi}</p>
<p><strong>Category:</strong> {category}</p>
</div>
)}
</div>
);
}
export default BMICalculator;App.js
import BMICalculator from "./BMICalculator";
export default function App() {
return <BMICalculator />;
}Image Carousel
Description
The image carousel component takes in an array of image URLs. Example image URLs are provided in the skeleton code.
Layout and positioning:
* The image carousel should be centered on the screen with a maximum size of 600px by 400px. Images should shrink to fit within the carousel so that the entire image is visible.
* Empty parts of the carousel can be filled with black.
* If the screen width is smaller than the image, the carousel should be resized to fit within the available horizontal space.
Navigation:
* Left and Right Navigation Buttons to allow users to navigate through the images.
The left button (®) must have id=”Previous”.
The right button (E) must have id=”Next”.
* The buttons should allow a cycling behavior, i.e. after the last image, the image cycles back to the first.
* Add page buttons at the bottom to directly jump to an image. You may assume there will be fewer than 10 images.
* Each page button must have a unique id in the format pageButton-<index> (e.g., pageButton-0, pageButton-1).
* A message “No images available.” must be shown when the image array is empty.</index>
https://namastedev.com/practice/image-carousel
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;
}ImageCarousel.jsx
import React, { useState } from "react";
import "./styles.css";
const ImageCarousel = ({ images = [] }) => {
const [currentIndex, setCurrentIndex] = useState(0);
const hasImages = images.length > 0;
const goToPrevious = () => {
setCurrentIndex((prev) => (prev === 0 ? images.length - 1 : prev - 1));
};
const goToNext = () => {
setCurrentIndex((prev) => (prev === images.length - 1 ? 0 : prev + 1));
};
const goToPage = (index) => {
setCurrentIndex(index);
};
if (!hasImages) {
return (
<div style={{ textAlign: "center", marginTop: "20px" }}>
No images available.
</div>
);
}
return (
<div
style={{
maxWidth: "600px",
width: "100%",
margin: "0 auto",
textAlign: "center",
position: "relative",
}}
>
<div
style={{
backgroundColor: "black",
height: "400px",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<img
src={images[currentIndex].src}
alt={images[currentIndex].alt}
style={{
maxWidth: "100%",
maxHeight: "100%",
objectFit: "contain",
}}
/>
</div>
{/* Navigation Arrows */}
<button
id="Previous"
onClick={goToPrevious}
style={{
position: "absolute",
top: "50%",
left: "10px",
transform: "translateY(-50%)",
fontSize: "24px",
background: "rgba(255,255,255,0.7)",
border: "none",
cursor: "pointer",
padding: "8px",
}}
>
◀
</button>
<button
id="Next"
onClick={goToNext}
style={{
position: "absolute",
top: "50%",
right: "10px",
transform: "translateY(-50%)",
fontSize: "24px",
background: "rgba(255,255,255,0.7)",
border: "none",
cursor: "pointer",
padding: "8px",
}}
>
▶
</button>
{/* Pagination Buttons */}
<div style={{ marginTop: "10px" }}>
{images.map((_, index) => (
<button
key={index}
id={`pageButton-${index}`}
onClick={() => goToPage(index)}
style={{
margin: "0 4px",
padding: "5px 10px",
borderRadius: "50%",
backgroundColor: index === currentIndex ? "#333" : "#ddd",
color: index === currentIndex ? "white" : "black",
border: "none",
cursor: "pointer",
}}
>
{index + 1}
</button>
))}
</div>
</div>
);
};
export default ImageCarousel;App.js
import ImageCarousel from './ImageCarousel'
export default function App() {
const images = [
{
src: 'https://do6gp1uxl3luu.cloudfront.net/question-webp/image-carousel1.jpg',
alt: 'nature',
},
{
src: 'https://do6gp1uxl3luu.cloudfront.net/question-webp/image-carousel2.jpg',
alt: 'Beach',
},
{
src: 'https://do6gp1uxl3luu.cloudfront.net/question-webp/image-carousel3.jpg',
alt: 'Yak',
},
{
src: 'https://do6gp1uxl3luu.cloudfront.net/question-webp/image-carousel4.jpg',
alt: 'Hay',
},
{
src: 'https://do6gp1uxl3luu.cloudfront.net/question-webp/image-carousel5.jpg',
alt: 'Plants',
},
{
src: 'https://do6gp1uxl3luu.cloudfront.net/question-webp/image-carousel6.jpg',
alt: 'Building',
},
];
return (
<>
<h2>Image Carousel</h2>
<ImageCarousel images={images} />
</>
)
}Cards Carousel
Description
Build a card carousel component that displays one card at a time with navigation buttons (“Previous” and “Next” to scroll through a list of cards.
Requirements
* The component accepts a prop cards (an array of objects with title and description).
* Initially shows the first card with title and description.
* Clicking “Next” shows the next card, and “Previous” shows the previous one.
* Buttons should be disabled appropriately at the start/end of the list.
* Display the card number like “1 of 5”.
Constraints & Edge Cases
* If cards array is empty, show “No cards available”.
* Buttons must be disabled when there’s no next or previous card.
* Should handle single-card array gracefully.
https://namastedev.com/practice/cards-carousel
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;
}Carousel.js
import React, { useState } from "react";
import "./styles.css";
function Carousel({ cards = [] }) {
const [currentIndex, setCurrentIndex] = useState(0);
if (cards.length === 0) {
return <div style={{ textAlign: "center", marginTop: "20px" }}>No cards available</div>;
}
const currentCard = cards[currentIndex];
const handlePrevious = () => {
if (currentIndex > 0) setCurrentIndex(currentIndex - 1);
};
const handleNext = () => {
if (currentIndex < cards.length - 1) setCurrentIndex(currentIndex + 1);
};
return (
<div
style={{
maxWidth: "400px",
margin: "40px auto",
padding: "20px",
border: "1px solid #ccc",
borderRadius: "10px",
textAlign: "center",
}}
>
<h2>{currentCard.title}</h2>
<p>{currentCard.description}</p>
<div style={{ marginTop: "20px" }}>
<button onClick={handlePrevious} disabled={currentIndex === 0} style={{ marginRight: "10px" }}>
Previous
</button>
<button onClick={handleNext} disabled={currentIndex === cards.length - 1}>
Next
</button>
</div>
<div style={{ marginTop: "10px", color: "#666" }}>
{currentIndex + 1} of {cards.length}
</div>
</div>
);
}
export default Carousel;App.js
import Carousel from "./Carousel";
export default function App() {
const cards = [
{ title: "Card 1", description: "Description for Card 1" },
{ title: "Card 2", description: "Description for Card 2" },
{ title: "Card 3", description: "Description for Card 3" },
];
return <Carousel cards={cards} />;
}Sortable List
Description
Create a React component called SortableList that allows users to add new items to a list and sort the list either in ascending or descending order. The sorting action should be optimized using the useCallback hook to avoid unnecessary re-renders of the sorting function when the list or the order is updated.
You need to:
1. Implement an input field with the placeholder “Add a new item”
2. A button labelled “Add Item” to add new items to the list.
3. Ensure each item in the list has a unique id in the format item-<index>.
4. Implement two buttons labelled "Sort Ascending" and
"Sort Descending" to sort the list in ascending or descending order.
5. Use JavaScript's built-in .sort) method to sort the list items alphabetically.
6. Use useCallback to memoize the sorting function to avoid unnecessary re-creation of the function during re-renders.
7. The list should dynamically update after each operation (adding items or sorting).</index>
https://namastedev.com/practice/sortable-list
styles.css
.sortable-list-container {
max-width: 400px;
margin: 0 auto;
padding: 20px;
border-radius: 8px;
display:flex;
flex-direction:column;
align-items:center;
gap:20px
}
.sortable-list-container h3 {
text-align: center;
color: #333;
}
.sortable-list-container input {
width: calc(100% - 90px);
padding: 8px;
margin-right: 10px;
border: 1px solid #ccc;
border-radius: 4px;
}
.sortable-list-container button {
padding: 8px 12px;
border: none;
background-color: #007bff;
color: white;
border-radius: 4px;
cursor: pointer;
margin:5px
}
.sortable-list-container button:hover {
background-color: #0056b3;
}
.sortable-list-container div {
text-align: center;
margin-bottom: 20px;
}
.list-items {
background-color: #e9ecef;
padding:20px;
width:100%;
display:flex;
flex-direction:column;
align-items:start
}
```
SortableList.js
import React, { useState, useCallback } from ‘react’;
import ‘./styles.css’;
const SortableList = ({ items = [] }) => {
const [inputValue, setInputValue] = useState(‘’);
const [list, setList] = useState(items);
// Add new item to the list
const handleAddItem = () => {
if (inputValue.trim() === ‘’) return;
setList([…list, inputValue.trim()]);
setInputValue(‘’);
};
// Memoized ascending sort function
const sortAscending = useCallback(() => {
setList(prevList => […prevList].sort((a, b) => a.localeCompare(b)));
}, []);
// Memoized descending sort function
const sortDescending = useCallback(() => {
setList(prevList => […prevList].sort((a, b) => b.localeCompare(a)));
}, []);
return (
<div className="sortable-list-container">
<h3>Sortable List</h3>
<div style={{ display: ‘flex’, width: ‘100%’ }}>
<input
type=”text”
placeholder=”Add a new item”
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
/>
<button onClick={handleAddItem}>Add Item</button>
</div>
<div>
<button onClick={sortAscending}>Sort Ascending</button>
<button onClick={sortDescending}>Sort Descending</button>
</div>
<div className="list-items">
{list.map((item, index) => (
<div key={`item-${index}`} id={`item-${index}`}>
{item}
</div>
))}
</div>
</div> ); };export default SortableList;
~~~
App.js
import SortableList from './SortableList'
const App = () => {
const items = ['Apple', 'Banana', 'Orange', 'Grapes', 'Mango'];
return <SortableList items={items} />;
};
export default App;Star Rating
Description
You need to implement a star rating component where users can select a rating by clicking on stars. The rating should update dynamically, and there should be a reset button to clear the rating.
Requirements
* The component should display five stars *****.
* Clicking on a star should update the rating and highlight the selected stars.
* The highlighted stars should be yellow (#FFD700), and unselected stars should be gray (#CCCCC).
* There should be a reset button below the stars that resets the rating to 0. The button should be labelled
“Reset Rating”.
* Display the current rating below the stars with the text :
“Current Rating: X”, where X is the selected star rating.
* The default rating should be 0 (no stars selected).
https://namastedev.com/practice/star-rating
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;
}StarRating.js
// StarRating.js
import React, { useState } from "react";
function StarRating() {
// Step 1: Create state variable for rating
const [rating, setRating] = useState(0);
// Function to render stars dynamically
const renderStars = () => {
const stars = [];
for (let i = 1; i <= 5; i++) {
stars.push(
<span
key={i}
onClick={() => setRating(i)}
style={{
fontSize: "2rem",
cursor: "pointer",
color: i <= rating ? "#FFD700" : "#CCCCCC",
}}
>
★
</span>
);
}
return stars;
};
// Reset handler
const resetRating = () => {
setRating(0);
};
return (
<div style={{ textAlign: "center", padding: "20px" }}>
<h1>Star Rating</h1>
<h3>by NamasteDev</h3>
{/* Step 2: Render 5 stars */}
<div>{renderStars()}</div>
{/* Step 5: Display current rating */}
<p>Current Rating: {rating}</p>
{/* Step 6: Reset button */}
<button onClick={resetRating}>Reset Rating</button>
</div>
);
}
export default StarRating;App.js
import StarRating from "./StarRating";
import './styles.css'
export default function App() {
return <StarRating />;
}Grid Lights
Requirements:
You are tasked with simulating a grid of lights that can be toggled on or off. The grid is initially in the “off” state. Each light in the grid can be toggled by clicking on it. When a light is toggled, the light itself and all adjacent lights (horizontally and vertically) also toggle their states.
The grid is composed of n x n cells, and each cell represents a light that can either be on (1) or off (0). The grid starts with all lights in the off state.
You need to implement a solution that allows toggling the lights and keeps track of the current state of the grid. Make sure to use inline css.
Component Structure:
* A single functional component named GridLights.
* Grid size n should be passed as a prop (default to 5 if not provided).
State Management:
* Use useState to manage the grid state.
* Maintain a 2D array to represent the current on/off state of each light.
Ul Behavior:
* Display the grid using simple square cells.
* A light in the “on” state should be visually different and have background color “gold”.
* A light in the “off” state should have a neutral background color “lightgray”.
* Clicking on a cell toggles the state of:
The clicked cell
The top neighbor (if it exists)
The bottom neighbor (if it exists)
The left neighbor (if it exists)
The right neighbor (if it exists)
* Each cell should have role=”cell”
User Interaction:
* Clicking on a cell updates the grid immediately.
* Ul should be responsive and update in real-time based on state.
https://namastedev.com/practice/grid-lights
GridLights.jsx
// GridLights.jsx
import React, { useState } from "react";
const GridLights = ({ n = 5 }) => {
const [grid, setGrid] = useState(
Array.from({ length: n }, () => Array(n).fill(0))
);
const toggleLight = (row, col) => {
const newGrid = grid.map((r) => [...r]);
const toggle = (i, j) => {
if (i >= 0 && i < n && j >= 0 && j < n) {
newGrid[i][j] = newGrid[i][j] === 1 ? 0 : 1;
}
};
toggle(row, col); // current
toggle(row - 1, col); // top
toggle(row + 1, col); // bottom
toggle(row, col - 1); // left
toggle(row, col + 1); // right
setGrid(newGrid);
};
return (
<div>
<h2 style={{ textAlign: "center" }}>Grid Lights</h2>
<div
style={{
display: "grid",
gridTemplateColumns: `repeat(${n}, 40px)`,
gap: "5px",
justifyContent: "center",
}}
>
{grid.map((row, i) =>
row.map((cell, j) => (
<div
key={`${i}-${j}`}
role="cell"
onClick={() => toggleLight(i, j)}
style={{
width: "40px",
height: "40px",
backgroundColor: cell === 1 ? "gold" : "lightgray",
border: "1px solid #ccc",
cursor: "pointer",
transition: "background-color 0.2s ease",
}}
></div>
))
)}
</div>
</div>
);
};
export default GridLights;App.js
import GridLights from './GridLights'
export default function App() {
return <GridLights/>
}Calculator
Create a simple responsive calculator component that performs basic arithmetic operations. It should feature interactive buttons and intuitive behavior for clear, backspace, and evaluation. The operators should be displayed using icons.
Requirements
* A container for visual calculator boundary.
* A display area implemented using an input field:
Placeholder text: “Enter expression”
Dynamically updates with user input
Displays both ongoing expressions and evaluation results
* Input must support the following characters:
Numbers: 0-9
Operators: +, -, *, /,%, v
Parentheses: ( and )
Decimal: *
* Button Grid:
A grid layout of numeric and operator buttons
Pressing = evaluates the expression
Clear button resets the entire input
Backspace button removes the last character only
* Use icons from lucide-react for enhanced visual clarity:
Clear - Trash
Backspace -> Delete
= > Equals
% -> Percent
/ -> Divide
→> Minus
> Plus
V -> Radical
* Invalid expressions should display Error in the input box
Edge Cases & Constraints
* Initial state: display is empty
* Invalid expressions should not break app
* Repeated operators must be handled gracefully
* Equal button should safely evaluate valid expressions
* Icons must respond on click and be testable
* Display should align text to the right and scroll if needed
Example Behaviors
// Example 1: User inputs "7 * 8" Display: "7*8" On "=" click → Display: "56" // Example 2: Clear Display: "56" → Click Clear (Trash icon) → "" // Example 3: Backspace Display: "5+" → Click Delete icon → "5" // Example 4: Invalid Display: "+" → "=" → Error
Testing Requirements
Data Test IDs (Required for Testing)
General Elements
* data-testid=”calc-container”. Test for rendering the container
* data-testid=”calc-display” - Calculator input display field with placeholder
Buttons
* data-testid=”btn-clear” - Clear all input
* data-testid=”btn-sqrt” - Square root operator (V)
* data-testid=”btn-modulus” - Modulus operator (%)
* data-testid=”btn-divide” - Division operator (/)
* data-testid=”btn-7” - Number 7
* data-testid=”btn-8” - Number 8
* data-testid=”btn-g” - Number 9
* data-testid=”btn-multiply” - Multiplication
operator ()
* data-testid=”btn-4” - Number 4
* data-testid=”btn-5” - Number 5
* data-testid=”btn-6” - Number 6
* data-testid=”btn-minus” - Subtraction operator
(-)
* data-testid=”btn-1” - Number 1
* data-testid=”btn-2” - Number 2
* data-testid=”btn-3” - Number 3
* data-testid=”btn-plus” - Addition operator (+)
* data-testid=”btn-0” - Number 0
* data-testid=”btn-dot” - Decimal point ()
* data-testid=”btn-open” - Open parenthesis (
* data-testid=”btn-close” - Close parenthesis )
* data-testid=”btn-back” - Backspace/delete last character
* data-testid=”btn-equal” - Evaluate expression (=)
Icon-Specific Elements
* data-testid=”icon-clear” - Icon inside clear
(Trash)
* data-testid=”icon-backspace” - Icon inside
backspace (Delete)
* data-testid=”icon-equals” - Icon inside equals
button (Equals)
* data-testid=”icon-sqrt”- Icon inside Squareroot
button (Radical)
* data-testid=”icon-percent” - Icon inside
modulus button (Percent)
* data-testid=”icon-divide” - Icon inside divide
button (Divide)
* data-testid=”icon-multiply”- Icon inside
multiply button (X)
* data-testid=”icon-minus”- Icon inside minus
button (Minus)
* data-testid=”icon-plus” - Icon inside plus button
(Plus)
https://namastedev.com/practice/calculator
styles.css
body {
margin: 0;
font-family: "Poppins", sans-serif;
background: linear-gradient(135deg, #f4f4f4, #dfe6e9);
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.calculator-container {
background-color: #ffffff;
padding: 30px 20px;
border-radius: 20px;
box-shadow: 0px 12px 24px rgba(0, 0, 0, 0.15);
width: 320px;
text-align: center;
}
.title {
font-size: 25px;
margin-bottom: 20px;
margin-top: 8px;
color: #22b691;
}
.display {
width: 90%;
height: 40px;
font-size: 20px;
padding: 10px;
border-radius: 10px;
border: 2px solid #000000;
text-align: right;
margin-bottom: 25px;
background-color: #e6efe7;
color: #2f3542;
}
.display::placeholder {
color: #b2bec3;
}
.button-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
}
button {
padding: 15px 0;
font-size: 18px;
border: none;
border-radius: 10px;
background-color: #d2f6ec;
color: #2d3436;
cursor: pointer;
transition: all 0.2s ease;
}
button:hover {
background-color: #a6dfc1;
transform: scale(1.05);
}
/*special buttons*/
.clear-btn {
background-color: #2d3436;
}
.clear-btn:hover {
background-color: #000000;
}
.back-btn {
background-color: #fd5a5a;
}
.back-btn:hover {
background-color: #ff2b2b;
}
.equal-btn {
grid-column: span 3;
background-color: #00b894;
}
.equal-btn:hover {
background-color: #019875;
}
/* Icon Styling for Lucide icons */
button svg {
width: 20px;
height: 20px;
vertical-align: middle;
stroke: #2d3436;
}
/* Icon colors for special buttons */
.clear-btn svg {
stroke: #ffffff;
}
.back-btn svg {
stroke: #ffffff;
}
.equal-btn svg {
stroke: #ffffff;
}Calculator.js
import React, { useState } from "react";
import "./styles.css";
import {
Trash,
Delete,
Equal as Equals,
Percent,
Divide,
Minus,
Plus,
Radical,
X,
} from "lucide-react";
function Calculator() {
const [expression, setExpression] = useState("");
const appendToExpression = (value) => {
setExpression((prev) => prev + value);
};
const handleBackspace = () => {
setExpression((prev) => prev.slice(0, -1));
};
const handleClear = () => {
setExpression("");
};
const handleEvaluate = () => {
try {
// Replace √ with Math.sqrt syntax
let sanitized = expression.replace(/v/g, "Math.sqrt");
// Evaluate
const result = eval(sanitized); // Safe here because user controls input
setExpression(result.toString());
} catch {
setExpression("Error");
}
};
return (
<div className="calculator-container" data-testid="calc-container">
<h1 className="title">Simple Calculator</h1>
<input
className="display"
data-testid="calc-display"
placeholder="Enter expression"
readOnly
value={expression}
/>
<div className="button-grid">
<button className="clear-btn" data-testid="btn-clear" onClick={handleClear}>
<Trash data-testid="icon-clear" />
</button>
<button className="icon-sqrt" data-testid="btn-sqrt" onClick={() => appendToExpression("v")}>
<Radical data-testid="icon-sqrt" />
</button>
<button
data-testid="btn-modulus"
onClick={() => appendToExpression("%")}
>
<Percent data-testid="icon-percent" />
</button>
<button
data-testid="btn-divide"
onClick={() => appendToExpression("/")}
>
<Divide data-testid="icon-divide" />
</button>
<button data-testid="btn-7" onClick={() => appendToExpression("7")}>
7
</button>
<button data-testid="btn-8" onClick={() => appendToExpression("8")}>
8
</button>
<button data-testid="btn-g" onClick={() => appendToExpression("9")}>
9
</button>
<button
data-testid="btn-multiply"
onClick={() => appendToExpression("*")}
>
<X data-testid="icon-multiply" />
</button>
<button data-testid="btn-4" onClick={() => appendToExpression("4")}>
4
</button>
<button data-testid="btn-5" onClick={() => appendToExpression("5")}>
5
</button>
<button data-testid="btn-6" onClick={() => appendToExpression("6")}>
6
</button>
<button
data-testid="btn-minus"
onClick={() => appendToExpression("-")}
>
<Minus data-testid="icon-minus" />
</button>
<button data-testid="btn-1" onClick={() => appendToExpression("1")}>
1
</button>
<button data-testid="btn-2" onClick={() => appendToExpression("2")}>
2
</button>
<button data-testid="btn-3" onClick={() => appendToExpression("3")}>
3
</button>
<button
data-testid="btn-plus"
onClick={() => appendToExpression("+")}
>
<Plus data-testid="icon-plus" />
</button>
<button data-testid="btn-0" onClick={() => appendToExpression("0")}>
0
</button>
<button data-testid="btn-dot" onClick={() => appendToExpression(".")}>
.
</button>
<button
data-testid="btn-open"
onClick={() => appendToExpression("(")}
>
(
</button>
<button
data-testid="btn-close"
onClick={() => appendToExpression(")")}
>
)
</button>
<button
className="back-btn"
data-testid="btn-back"
onClick={handleBackspace}
>
<Delete data-testid="icon-backspace" />
</button>
<button
className="equal-btn"
data-testid="btn-equal"
onClick={handleEvaluate}
>
<Equals data-testid="icon-equals" />
</button>
</div>
</div>
);
}
export default Calculator;App.js
import Calculator from './Calculator.js'
export default function App() {
return <Calculator/>
}Tic Tac Toe
Build a simple Tic Tac Toe game using React. This game allows two players to take turns and play on a 3x3 board. The goal is to get three of the same symbol (X or O) in a row (horizontally, vertically, or diagonally).
Functional Requirements:
1. Render a 3x3 grid of squares.
2. Each player takes a turn placing “X” or “O”.
3. Show a message like “Next Player: X” or “Next Player: O”.
4. Detect the winner or a draw and show the result.
5. Disable all squares after a win or draw.
6. Include a “Restart Game” button to reset the board. The restart button must have an id=”restart”.
7. Each cell must have an id in the format cell-o to cell-8, corresponding to its position.
8. You must display a message showing the current status of the game. This message should appear at all times and clearly indicate one of the following:
9. Possible Status Messages:
* Next Player: X- when it’s X’s turn.
* Next Player: 0 - when it’s O’s turn.
* Winner: X - when player X wins.
* Winner: 0 - when player O wins.
* It’s a Draw! - when all 9 cells are filled and there’s no winner.
10. The element that displays this message must have the HTML id=”status”, so it can be accessed easily in automated tests.
https://namastedev.com/practice/tic-tac-toe
styles.css
.game {
text-align: center;
margin-top: 50px;
font-family: sans-serif;
}
.board {
display: grid;
grid-template-columns: repeat(3, 80px);
grid-gap: 10px;
justify-content: center;
margin: 20px auto;
}
.square {
width: 80px;
height: 80px;
font-size: 2rem;
font-weight: bold;
cursor: pointer;
background-color: #f0f0f0;
border: 2px solid #999;
border-radius: 8px;
}
.restart-button {
margin-top: 20px;
padding: 10px 20px;
font-size: 1rem;
}TicTacToe.js
import React, { useState } from "react";
import "./styles.css";
function TicTacToe() {
const initialBoard = Array(9).fill(null);
const [board, setBoard] = useState(initialBoard);
const [isXNext, setIsXNext] = useState(true);
const winner = calculateWinner(board);
const isDraw = !winner && board.every((cell) => cell !== null);
// Status Message
let statusMessage = "";
if (winner) {
statusMessage = `Winner: ${winner}`;
} else if (isDraw) {
statusMessage = "It's a Draw!";
} else {
statusMessage = `Next Player: ${isXNext ? "X" : "O"}`;
}
const handleClick = (index) => {
if (board[index] || winner || isDraw) return; // Disable moves after game ends
const newBoard = [...board];
newBoard[index] = isXNext ? "X" : "O";
setBoard(newBoard);
setIsXNext(!isXNext);
};
const restartGame = () => {
setBoard(initialBoard);
setIsXNext(true);
};
return (
<div className="game">
<h1>Tic Tac Toe</h1>
{/* STATUS MESSAGE */}
<h2 id="status">{statusMessage}</h2>
{/* GAME BOARD */}
<div className="board">
{board.map((value, index) => (
<button
key={index}
id={`cell-${index}`}
className="square"
onClick={() => handleClick(index)}
disabled={winner || isDraw}
>
{value}
</button>
))}
</div>
{/* RESTART BUTTON */}
<button id="restart" className="restart-button" onClick={restartGame}>
Restart Game
</button>
</div>
);
}
// Helper function to check winner
function calculateWinner(board) {
const winningCombos = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
];
for (let [a, b, c] of winningCombos) {
if (board[a] && board[a] === board[b] && board[a] === board[c]) {
return board[a];
}
}
return null;
}
export default TicTacToe;App.js
import TicTacToe from './TicTacToe'
export default function App() {
return <TicTacToe/>
}Dice Roller
Create a Dice Roller Component that simulates rolling a dice when a button is clicked. The dice should display a random number between 1 and 6.
Requirements
1. The component should have a button labeled “Roll Dice”.
2. Clicking the button should generate a random number between 1 and 6.
3. The rolled number should be displayed on the screen with text =”🎲 diceValue”.
4. If the Roll Dice button is not clicked, then show a message “Click to roll!”
5. The Ul should include a dice-like representation (optional).
Constraints & Edge Cases
* Constraint 1: The rolled number should always be between 1 and 6.
* Constraint 2: Ensure the result updates correctly on every button click.
* Edge Case 1: Handle cases where the button is clicked multiple times quickly.
* Edge Case 2: Ensure Ul updates correctly when the dice is rolled.
https://namastedev.com/practice/dice-roller
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;
}.dice-container {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 50px;
}
button {
padding: 10px 20px;
font-size: 1rem;
margin-bottom: 20px;
cursor: pointer;
}
.dice-display p {
font-size: 1.2rem;
margin-top: 10px;
}
/* Dice UL Grid */
.dice-grid {
display: grid;
grid-template-columns: repeat(3, 20px);
grid-template-rows: repeat(3, 20px);
gap: 5px;
list-style: none;
padding: 0;
margin: 10px 0;
}
.dice-grid li {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: #eee;
}
.dice-grid li.dot {
background-color: #333;
}DiceRoller.js
import React, { useState } from "react";
import "./styles.css";
function DiceRoller() {
const [diceValue, setDiceValue] = useState(null);
const rollDice = () => {
// Generate a random number between 1 and 6
const value = Math.floor(Math.random() * 6) + 1;
setDiceValue(value);
};
// // Dice dots representation
// const renderDiceFace = (value) => {
// const dots = {
// 1: [4],
// 2: [0, 8],
// 3: [0, 4, 8],
// 4: [0, 2, 6, 8],
// 5: [0, 2, 4, 6, 8],
// 6: [0, 2, 3, 5, 6, 8],
// };
// return (
// <ul className="dice-grid">
// {Array(9)
// .fill(0)
// .map((_, index) => (
// <li
// key={index}
// className={dots[value]?.includes(index) ? "dot" : ""}
// ></li>
// ))}
// </ul>
// );
// };
// const getDieSymbol = (value) => {
// const symbols = ["⚀", "⚁", "⚂", "⚃", "⚄", "⚅"];
// return symbols[value - 1] || "";
// };
return (
<div className="dice-container">
<h1>Dice Roller</h1>
<button onClick={rollDice}>Roll Dice</button>
<div className="dice-display">
{diceValue ? (
<>
{
// renderDiceFace(diceValue)
}
<p>🎲 {diceValue}</p>
</>
) : (
<p>Click to roll!</p>
)}
</div>
</div>
);
}
export default DiceRoller;App.js
import DiceRoller from "./DiceRoller";
export default function App() {
return <DiceRoller />;
}Typewriter Message
Animation
* Display text character by character with 100ms delay for authentic typewriter feel
* Show animated blinking cursor during typing animation for visual appeal
Controls
* Include “Start” button to initiate the typewriter effect
* Include “Skip” button to instantly reveal the full message for impatient users
* Include “Next” button to cycle through different inspirational messages
* Show message counter (e.g., “Message 1 of 5”) to track progress
* Disable buttons appropriately during animation to prevent conflicts
Functionality
* Clear Interval when animation completes or is skipped for proper cleanup
* Use useEffect and setinterval for precise timing control
* Implement string sileing for character-by-character display
* Manage multiple messages with seamless cycling functionality
Required Elements and Attributes for Testing
Message Display
* Must contain text element showing partial or full message
Blinking Cursor
* Must have animated cursor during typing
Buttons
* Start Button → Must contain visible text “Start” and have onClick handler
* Skip Button → Must contain visible text “Skip” and have onClick handler
* Next Button → Must contain visible text “Next” and have onClick handler
Message Counter
* Must display “Message X of Y” format
Button States
* Buttons must be disabled during typing animation
Animation Speed
* Characters must appear with 100ms interval
https://namastedev.com/practice/typewriter-message
styles.css
.typewriter-container {
text-align: center;
font-family: "Courier New", monospace;
margin-top: 40px;
max-width: 800px;
margin-left: auto;
margin-right: auto;
padding: 20px;
background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%);
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
}
.typewriter-container h1 {
color: #ecf0f1;
font-size: 2.5rem;
margin-bottom: 30px;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
}
.message-display {
background: rgba(0, 0, 0, 0.8);
border: 2px solid #3498db;
border-radius: 10px;
padding: 30px;
margin: 20px 0;
min-height: 120px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.displayed-text {
font-size: 1.4rem;
color: #2ecc71;
margin: 0;
line-height: 1.6;
text-align: left;
font-family: "Courier New", monospace;
}
.cursor {
color: #e74c3c;
font-weight: bold;
animation: blink 1s infinite;
margin-left: 2px;
}
@keyframes blink {
0%,
50% {
opacity: 1;
}
51%,
100% {
opacity: 0;
}
}
.controls {
display: flex;
justify-content: center;
gap: 15px;
margin: 30px 0;
flex-wrap: wrap;
}
.controls button {
padding: 12px 24px;
font-size: 1.1rem;
font-weight: bold;
border: none;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
font-family: "Courier New", monospace;
min-width: 120px;
}
.start-button {
background: #27ae60;
color: white;
}
.start-button:hover:not(:disabled) {
background: #229954;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(39, 174, 96, 0.4);
}
.skip-button {
background: #e67e22;
color: white;
}
.skip-button:hover {
background: #d35400;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(230, 126, 34, 0.4);
}
.next-button {
background: #3498db;
color: white;
}
.next-button:hover:not(:disabled) {
background: #2980b9;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(52, 152, 219, 0.4);
}
.controls button:disabled {
background: #95a5a6;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.message-info {
margin-top: 30px;
color: #bdc3c7;
}
.message-info p {
margin: 10px 0;
font-size: 1rem;
}
.instruction {
color: #ecf0f1;
font-style: italic;
font-size: 1.1rem;
}
/* Responsive design */
@media (max-width: 768px) {
.typewriter-container {
margin: 20px;
padding: 15px;
}
.typewriter-container h1 {
font-size: 2rem;
}
.displayed-text {
font-size: 1.2rem;
}
.controls {
flex-direction: column;
align-items: center;
}
.controls button {
width: 200px;
}
}App.js
import React from "react";
import { TypeWriterMessage } from "./TypeWriterMessage.js";
import "./styles.css";
function App() {
return (
<div className="app-wrapper">
<TypeWriterMessage />
</div>
);
}
export default App;TypeWriterMessage.js
import React, { useState, useEffect, useRef } from "react";
import "./styles.css";
const messages = [
"Hello, welcome to the typewriter effect!",
"This demonstrates useEffect and setInterval in React.",
"Watch as each character appears one by one.",
"You can skip the animation if you're impatient!",
"Thanks for watching the typewriter in action!",
];
export function TypeWriterMessage() {
const [currentMessageIndex, setCurrentMessageIndex] = useState(0);
const [displayedText, setDisplayedText] = useState("");
const [isTyping, setIsTyping] = useState(false);
const [showSkip, setShowSkip] = useState(false);
const intervalRef = useRef(null);
const indexRef = useRef(0); // <-- FIX: stable index
const startTyping = () => {
if (isTyping) return;
clearInterval(intervalRef.current); // <-- FIX: avoid overlapping timers
setDisplayedText("");
indexRef.current = 0; // start from first character every time
setIsTyping(true);
setShowSkip(true);
};
const skipTyping = () => {
clearInterval(intervalRef.current);
const fullMessage = messages[currentMessageIndex];
setDisplayedText(fullMessage);
setIsTyping(false);
setShowSkip(false);
};
const nextMessage = () => {
clearInterval(intervalRef.current);
setDisplayedText("");
indexRef.current = 0;
setIsTyping(false);
setShowSkip(false);
setCurrentMessageIndex((prev) => (prev + 1) % messages.length);
};
useEffect(() => {
if (!isTyping) return;
clearInterval(intervalRef.current); // prevent duplicates
const fullMessage = messages[currentMessageIndex];
intervalRef.current = setInterval(() => {
const i = indexRef.current;
if (i >= fullMessage.length) {
clearInterval(intervalRef.current);
setIsTyping(false);
setShowSkip(false);
return;
}
setDisplayedText((prev) => prev + fullMessage[i]);
indexRef.current += 1;
}, 100);
return () => clearInterval(intervalRef.current);
}, [isTyping, currentMessageIndex]);
return (
<div className="typewriter-container">
<h1>Typewriter Effect</h1>
<div className="message-display">
<p className="displayed-text">{displayedText}</p>
{isTyping && <span className="cursor">|</span>}
</div>
<div className="controls">
<button
onClick={startTyping}
className="start-button"
disabled={isTyping}
>
Start
</button>
{showSkip && (
<button
onClick={skipTyping}
className="skip-button"
disabled={!isTyping}
>
Skip
</button>
)}
<button
onClick={nextMessage}
className="next-button"
disabled={isTyping}
>
Next
</button>
</div>
<div className="message-info">
<p>
Message {currentMessageIndex + 1} of {messages.length}
</p>
<p className="instruction">
Watch the typewriter effect or use the Skip button to see the full
message instantly!
</p>
</div>
</div>
);
}Word Counter
Build a Word Frequency Counter that dynamically analyzes user input text and displays how often each word appears. The counter is case-insensitive, ignores special characters, and sorts the output in descending order of frequency.
Things to do
* Create a textarea where users can type or paste any text.
* Every time the text changes, update the word count list in real-time.
* Case-insensitive word matching
* Ignore symbols, numbers, punctuation, and extra white spaces.
* Sorts words by their frequency (most used on top)
* Each word should be shown with the number of times it occurs (e.g., hello:
3 Times).
Example
Input:
React, React Testing NamasteDev!
Output:
react count: 2 namastedev count: 1 testing count: 1
How It Works
1. User types into the textarea
2. On each character change:
* Text is cleaned: punctuation is removed, all words are lowercased.
* Words are split and counted.
* Results are sorted by frequency and displayed as a list.
Testing Instructions
Please use the following data-testid attributes in your React component:
* textarea → for the textarea input
* result-list → for the textarea input
* word-(word} → for each word e.g “word-react”
https://namastedev.com/practice/word-counter
styles.css
.wordCounter {
display: flex;
flex-direction: column;
align-items: center;
}
.container {
padding: 10px;
}
.textarea {
width: 300px;
height: 40px;
padding: 10px 15px;
}
ul li {
list-style-type: square;
}
li {
text-align: left;
margin: 5px 0;
}App.js
import WordCounter from './WordCounter.js'
export default function App() {
return <WordCounter/>
}WordCounter.js
import { useState, useEffect } from "react";
function WordCounter() {
const [text, setText] = useState("");
const [count, setCount] = useState([]);
// Count Logic
function handleCount(inputText) {
// 1. Convert to lowercase
let cleaned = inputText.toLowerCase();
// 2. Remove everything except letters & spaces
cleaned = cleaned.replace(/[^a-z\s]/g, " ");
// 3. Split into words, filter out empty strings
const words = cleaned.split(/\s+/).filter(Boolean);
// 4. Count occurrences
const freq = {};
words.forEach((w) => {
freq[w] = (freq[w] || 0) + 1;
});
// 5. Convert to array & sort by frequency
const sorted = Object.entries(freq).sort((a, b) => b[1] - a[1]);
setCount(sorted);
}
useEffect(() => {
handleCount(text);
}, [text]);
return (
<div className="wordCounter">
<h1>Word Counter</h1>
<div className="container">
<textarea
className="textarea"
placeholder="Type your text here"
data-testid="textarea"
value={text}
onChange={(e) => setText(e.target.value)}
></textarea>
<div className="results">
<h3>Word Frequencies</h3>
<ul data-testid="result-list">
{count.map(([word, countValue]) => (
<li key={word} data-testid={`word-${word}`}>
<strong>{word}</strong>: {countValue} Times
</li>
))}
</ul>
</div>
</div>
</div>
);
}
export default WordCounter;