This is a fundamental test of your understanding of state management using the useState
hook.
State allows a component to remember information and re-render when that information changes.
useState
returns an array with two elements: the current state value
and a function to update it.
// Counter.js
import React, { useState } from 'react';
function Counter() {
// Initialize state 'count' with a default value of 0
const [count, setCount] = useState(0);
return (
<div>
<h2>Count: {count}</h2>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={() => setCount(count - 1)}>Decrement</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
}
export default Counter;
This demonstrates handling boolean state. The core idea is to flip the current boolean value when an event occurs, typically a button click. The UI then conditionally renders different text or styles based on this boolean state.
// ToggleSwitch.js
import React, { useState } from 'react';
function ToggleSwitch() {
const [isOn, setIsOn] = useState(false);
const handleToggle = () => {
setIsOn(prevIsOn => !prevIsOn);
};
return (
<div>
<button onClick={handleToggle}>
{isOn ? 'ON' : 'OFF'}
</button>
<p>The switch is {isOn ? 'ON' : 'OFF'}.</p>
</div>
);
}
export default ToggleSwitch;
A classic. This tests your ability to manage an array of objects in state. Key operations include adding a new
item, removing an item (usually by its unique id
), and updating an item (like toggling its
completion status). This often involves using array methods like map
, filter
, and
creating new arrays to ensure immutability.
// TodoList.js
import React, { useState } from 'react';
function TodoList() {
const [todos, setTodos] = useState([]);
const [input, setInput] = useState('');
const addTodo = () => {
if (input.trim() === '') return;
setTodos([...todos, { id: Date.now(), text: input, completed: false }]);
setInput('');
};
const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
const toggleComplete = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
));
}
return (
<div>
<input type="text" value={input} onChange={e => setInput(e.target.value)} />
<button onClick={addTodo}>Add Todo</button>
<ul>
{todos.map(todo => (
<li key={todo.id} style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
<span onClick={() => toggleComplete(todo.id)}>{todo.text}</span>
<button onClick={() => deleteTodo(todo.id)}>X</button>
</li>
))}
</ul>
</div>
);
}
export default TodoList;
This is a core React concept.
4. Parent-to-Child: Data flows down via props. A parent component passes
data to its child by defining attributes on the child component tag in its JSX.
5. Child-to-Parent: Data flows up via callbacks. The parent passes a
function down to the child as a prop. The child can then call this function, passing data as an argument, which
the parent receives.
// ParentComponent.js
import React, { useState } from 'react';
import ChildComponent from './ChildComponent';
function ParentComponent() {
const [messageForChild, setMessageForChild] = useState('Hello from Parent!');
const [messageFromChild, setMessageFromChild] = useState('');
// Callback function to be passed to the child
const handleChildData = (data) => {
setMessageFromChild(data);
};
return (
<div>
<h2>Parent Component</h2>
<p>Message from child: {messageFromChild}</p>
<ChildComponent message={messageForChild} onData={handleChildData} />
</div>
);
}
// ChildComponent.js
function ChildComponent({ message, onData }) {
const [childInput, setChildInput] = useState('');
const sendDataToParent = () => {
onData(childInput); // Call the callback prop
}
return (
<div>
<h3>Child Component</h3>
<p>Message from parent: {message}</p>
<input type="text" value={childInput} onChange={e => setChildInput(e.target.value)} />
<button onClick={sendDataToParent}>Send Data to Parent</button>
</div>
);
}
This tests your ability to create "controlled components," where React state is the single source of truth for form inputs. You manage the form's data using `useState`, update the state on every keystroke with an `onChange` handler, and handle submission logic in an `onSubmit` handler.
// MyForm.js
import React, { useState } from 'react';
function MyForm() {
const [formData, setFormData] = useState({ username: '', password: '' });
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prevData => ({ ...prevData, [name]: value }));
};
const handleSubmit = (e) => {
e.preventDefault();
alert(`Username: ${formData.username}, Password: ${formData.password}`);
};
return (
<form onSubmit={handleSubmit}>
<input type="text" name="username" value={formData.username} onChange={handleChange} placeholder="Username"/>
<input type="password" name="password" value={formData.password} onChange={handleChange} placeholder="Password"/>
<button type="submit">Submit</button>
</form>
);
}
React renders lists of components using JavaScript's .map()
array method. The most important rule
is to assign a unique and stable key
prop to each list item. This key helps React identify which
items have changed, are added, or are removed, which is crucial for performance.
// UserList.js
function UserList() {
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' },
];
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
This is about displaying different UI elements based on a certain condition or state. Common techniques include
using an if
statement, the ternary operator (condition ? ... : ...
), or the logical
AND operator (condition && ...
).
// AuthStatus.js
import React, { useState } from 'react';
function AuthStatus() {
const [isLoggedIn, setIsLoggedIn] = useState(false);
return (
<div>
{isLoggedIn ? (
<p>Welcome back!</p>
) : (
<p>Please log in.</p>
)}
<button onClick={() => setIsLoggedIn(!isLoggedIn)}>
{isLoggedIn ? 'Logout' : 'Login'}
</button>
</div>
);
}
This tests your knowledge of side effects with the useEffect
hook and asynchronous operations. The
flow is:
1. Set loading
state to true
.
2. Fetch data inside useEffect
(with an empty dependency array []
to run only once
on mount).
3. If successful, store the data in state and set loading
to false
.
4. If it fails, store the error in an error
state and set loading
to
false
.
5. Conditionally render a loading indicator, an error message, or the data.
// DataFetcher.js
import React, { useState, useEffect } from 'react';
function DataFetcher() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
if (!response.ok) {
throw new Error('Network response was not ok');
}
const result = await response.json();
setData(result);
} catch (error) {
setError(error.message);
} finally {
setLoading(false);
}
};
fetchData();
}, []); // Empty array means this effect runs once on mount
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<div>
<h2>Fetched Data</h2>
<p>Title: {data?.title}</p>
</div>
);
}
This demonstrates the lifecycle management capabilities of useEffect
, specifically the cleanup
function. An interval is a side effect that needs to be "cleaned up" when the component unmounts to prevent
memory leaks. The function returned from `useEffect` serves this purpose.
// Timer.js
import React, { useState, useEffect } from 'react';
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const intervalId = setInterval(() => {
setSeconds(prevSeconds => prevSeconds + 1);
}, 1000);
// Cleanup function: runs when the component unmounts
return () => clearInterval(intervalId);
}, []); // Empty dependency array ensures this runs only once
return <h2>Timer: {seconds}s</h2>;
}
The useRef
hook is used to access DOM nodes directly. It creates a mutable object whose
.current
property is initialized to the passed argument. Attaching the ref to a DOM element's `ref`
attribute allows you to interact with that element programmatically, like calling .focus()
.
// FocusInput.js
import React, { useRef, useEffect } from 'react';
function FocusInput() {
const inputRef = useRef(null);
useEffect(() => {
// Focus the input element on component mount
inputRef.current.focus();
}, []);
return (
<input ref={inputRef} type="text" placeholder="I will be focused on load" />
);
}
React Router is the standard library for routing in React.
• BrowserRouter
: Wraps your app to enable routing.
• Routes
: A container for a collection of Route
components.
• Route
: Maps a URL path to a specific component.
• Link
: Creates navigation links without a full page reload.
• Outlet
: Used in parent route components to render their child route components.
react-router-dom
to run it.// App.js (Main Router Setup)
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">Home</Link> | <Link to="/about">About</Link> | <Link to="/dashboard">Dashboard</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/dashboard/*" element={<Dashboard />} /> // Parent route
</Routes>
</BrowserRouter>
);
}
// Dashboard.js (Nested Routing)
import { Routes, Route, Link, Outlet } from 'react-router-dom';
function Dashboard() {
return (
<div>
<h2>Dashboard</h2>
<nav>
<Link to="profile">Profile</Link> | <Link to="settings">Settings</Link>
</nav>
<Outlet /> // Renders the matched child route component here
<Routes>
<Route path="profile" element={<Profile />} />
<Route path="settings" element={<Settings />} />
</Routes>
</div>
);
}
The Context API solves the problem of "prop drilling" (passing props down through many levels of components).
It allows you to share state that can be considered "global" for a tree of React components.
1. Create Context: Use React.createContext()
.
2. Provide Context: Create a Provider component that holds the state and wraps the
component tree that needs access to it.
3. Consume Context: Use the useContext
hook in any child component to read the
context's value.
// ThemeContext.js
import React, { createContext, useState, useContext } from 'react';
const ThemeContext = createContext();
export const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light'));
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
export const useTheme = () => useContext(ThemeContext);
// SomeComponent.js - Example usage
import { useTheme } from './ThemeContext';
function ThemeTogglerButton() {
const { theme, toggleTheme } = useTheme();
return (
<button onClick={toggleTheme} style={{ background: theme === 'dark' ? '#333' : '#FFF', color: theme === 'dark' ? '#FFF' : '#333' }}>
Toggle to {theme === 'light' ? 'dark' : 'light'} mode
</button>
);
}
Similar to the Theme Switcher, this uses the Context API but with more complex state logic. The state can be an
array of cart items. The context provider would expose the cart array and functions like addToCart
,
removeFromCart
, etc. For more complex logic, the provider often uses the useReducer
hook internally.
This tests your understanding of preventing unnecessary re-renders.
• React.memo()
: A higher-order component that memoizes a functional component. It re-renders
only if its props change.
• useCallback()
: Memoizes a function. This is useful when passing callbacks to optimized child
components that rely on reference equality to prevent unnecessary re-renders.
• useMemo()
: Memoizes a value. Use it to avoid expensive calculations on every render.
const HeavyComponent = React.memo(function HeavyComponent({ onButtonClick }) {
// This component only re-renders if onButtonClick changes
return <button onClick={onButtonClick}>Click Me</button>;
});
function Parent() {
const [count, setCount] = useState(0);
// useMemo: Calculate a value only when 'count' changes
const expensiveValue = useMemo(() => {
return count * 2;
}, [count]);
// useCallback: Memoize the function so HeavyComponent doesn't re-render unnecessarily
const handleClick = useCallback(() => {
console.log('Button clicked!');
}, []); // Empty dependency array means the function is created only once
return (
<div>
<p>Count: {count}, Expensive Value: {expensiveValue}</p>
<button onClick={() => setCount(c => c + 1)}>Increment Count</button>
<HeavyComponent onButtonClick={handleClick} />
</div>
);
}
This common UI pattern involves taking a list of data and an input field. As the user types into the input, the list is filtered in real-time to show only the items that match the search query. This is achieved by holding the search term in state and computing a filtered list on every render.
// SearchFilter.js
import React, { useState } from 'react';
const initialItems = ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry', 'Fig'];
function SearchFilter() {
const [searchTerm, setSearchTerm] = useState('');
// Filter items based on the search term
const filteredItems = initialItems.filter(item =>
item.toLowerCase().includes(searchTerm.toLowerCase())
);
return (
<div>
<input
type="text"
placeholder="Search for a fruit..."
value={searchTerm}
onChange={e => setSearchTerm(e.target.value)}
/>
<ul>
{filteredItems.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
}
export default SearchFilter;
A modal is a dialog box that appears on top of the main content. Its visibility is controlled by a boolean
state variable (e.g., isModalOpen
). A button in the parent component sets this state to
true
to open it, and a close button inside the modal sets it back to false
. CSS is
used to create the overlay and center the modal content.
// Modal parent component
import React, { useState } from 'react';
function ModalContainer() {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<button onClick={() => setIsOpen(true)}>Open Modal</button>
<Modal isOpen={isOpen} onClose={() => setIsOpen(false)}>
<h2>Modal Title</h2>
<p>This is the content of the modal.</p>
</Modal>
</div>
);
}
// Modal.js - The actual modal component
function Modal({ isOpen, onClose, children }) {
if (!isOpen) return null;
const overlayStyle = {
position: 'fixed', top: 0, left: 0, right: 0, bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.7)',
display: 'flex', alignItems: 'center', justifyContent: 'center'
};
const modalStyle = {
background: 'white', padding: '20px', borderRadius: '5px', color: 'black'
};
return (
<div style={overlayStyle} onClick={onClose}>
<div style={modalStyle} onClick={e => e.stopPropagation()}>
{children}
<button onClick={onClose}>Close</button>
</div>
</div>
);
}
Debouncing prevents an expensive operation (like an API call) from firing on every single keystroke. Instead, it waits until the user has stopped typing for a specific duration. This is achieved with `useState` and `useEffect`. The effect watches for changes in the input value and sets a timer (`setTimeout`). If the input changes again before the timer finishes, the old timer is cleared and a new one is set.
// DebouncedSearch.js
import React, { useState, useEffect } from 'react';
function DebouncedSearch() {
const [inputValue, setInputValue] = useState('');
const [debouncedValue, setDebouncedValue] = useState('');
useEffect(() => {
// Set up a timer
const handler = setTimeout(() => {
setDebouncedValue(inputValue);
}, 500); // 500ms delay
// Clear the timer if the input value changes
return () => {
clearTimeout(handler);
};
}, [inputValue]); // Re-run the effect only when inputValue changes
// You would use 'debouncedValue' to make your API call
return (
<div>
<input
type="text"
value={inputValue}
onChange={e => setInputValue(e.target.value)}
placeholder="Search..."
/>
<p>Debounced Value: {debouncedValue}</p>
</div>
);
}
A multi-step form, or "wizard," breaks a long form into smaller, manageable pieces. The state management involves tracking the current step number and a single object containing all the form data from every step. Conditional rendering is used to display the correct component for the current step.
// MultiStepForm.js
import React, { useState } from 'react';
function MultiStepForm() {
const [step, setStep] = useState(1);
const [formData, setFormData] = useState({ name: '', email: '', password: '' });
const nextStep = () => setStep(prev => prev + 1);
const prevStep = () => setStep(prev => prev - 1);
const handleChange = input => e => {
setFormData({ ...formData, [input]: e.target.value });
};
switch (step) {
case 1:
return (<Step1 nextStep={nextStep} handleChange={handleChange} values={formData} />);
case 2:
return (<Step2 nextStep={nextStep} prevStep={prevStep} handleChange={handleChange} values={formData} />);
case 3:
return (<Confirmation prevStep={prevStep} values={formData} />);
default:
return (<div>Form Completed</div>);
}
}
// Note: Step1, Step2, and Confirmation would be separate components receiving these props.
React handles file uploads using a standard <input type="file" />
element. The selected file
is accessed from the `onChange` event via `event.target.files[0]`. To create a preview for an image, you can use
`URL.createObjectURL()` to generate a temporary local URL for the selected file, which can then be used as the
`src` for an `<img>` tag.
// FileUploader.js
import React, { useState } from 'react';
function FileUploader() {
const [selectedFile, setSelectedFile] = useState(null);
const [preview, setPreview] = useState(null);
const handleFileChange = (e) => {
const file = e.target.files[0];
if (file) {
setSelectedFile(file);
setPreview(URL.createObjectURL(file));
}
};
return (
<div>
<input type="file" onChange={handleFileChange} />
{selectedFile && <p>File: {selectedFile.name}</p>}
{preview && <img src={preview} alt="Preview" width="200" />}
</div>
);
}
A tabs component allows users to switch between different views within the same context. This is managed with a state variable that holds the index of the currently active tab. Clicking a tab button updates this state, and the component conditionally renders the content associated with the active index.
// Tabs.js
import React, { useState } from 'react';
const TABS_DATA = [
{ label: 'Tab 1', content: 'Content for Tab 1' },
{ label: 'Tab 2', content: 'Content for Tab 2' },
{ label: 'Tab 3', content: 'Content for Tab 3' },
];
function Tabs() {
const [activeTab, setActiveTab] = useState(0);
return (
<div>
<div className="tab-list">
{TABS_DATA.map((tab, index) => (
<button
key={index}
onClick={() => setActiveTab(index)}
style={{ fontWeight: activeTab === index ? 'bold' : 'normal' }}
>
{tab.label}
</button>
))}
</div>
<div className="tab-content">
<p>{TABS_DATA[activeTab].content}</p>
</div>
</div>
);
}
An accordion allows users to show and hide sections of content. It's managed with a state variable that stores the index of the currently open item. When a user clicks an item's header, the state is updated to that item's index. Clicking the same header again can set the state to a neutral value (like `null`) to close it.
// Accordion.js
import React, { useState } from 'react';
const ACCORDION_DATA = [
{ title: 'Section 1', content: 'Content for section 1.' },
{ title: 'Section 2', content: 'Content for section 2.' },
{ title: 'Section 3', content: 'Content for section 3.' },
];
function Accordion() {
const [openIndex, setOpenIndex] = useState(null);
const handleItemClick = (index) => {
setOpenIndex(openIndex === index ? null : index);
};
return (
<div>
{ACCORDION_DATA.map((item, index) => (
<div key={index} className="accordion-item">
<div className="accordion-title" onClick={() => handleItemClick(index)}>
{item.title}
</div>
{openIndex === index && (
<div className="accordion-content">{item.content}</div>
)}
</div>
))}
</div>
);
}
A carousel displays a series of items one at a time. Its core logic relies on a state variable for the `currentIndex`. "Next" and "Previous" buttons increment or decrement this index. Logic is required to handle the boundaries: when the user clicks "Next" on the last slide, the index should loop back to 0, and vice-versa for the "Previous" button.
// Carousel.js
import React, { useState } from 'react';
const SLIDES = ['Slide 1', 'Slide 2', 'Slide 3', 'Slide 4'];
function Carousel() {
const [currentIndex, setCurrentIndex] = useState(0);
const goToPrevious = () => {
const isFirstSlide = currentIndex === 0;
const newIndex = isFirstSlide ? SLIDES.length - 1 : currentIndex - 1;
setCurrentIndex(newIndex);
};
const goToNext = () => {
const isLastSlide = currentIndex === SLIDES.length - 1;
const newIndex = isLastSlide ? 0 : currentIndex + 1;
setCurrentIndex(newIndex);
};
return (
<div style={{ display: 'flex', alignItems: 'center' }}>
<button onClick={goToPrevious}>← Prev</button>
<div style={{ margin: '0 20px' }}>
<h2>{SLIDES[currentIndex]}</h2>
</div>
<button onClick={goToNext}>Next →</button>
</div>
);
}