ETE Practice Questions
INT252
(Unit I - JavaScript Refresher: Array Methods & ES6)
Create a JavaScript module script that processes a raw dataset of employee objects.
Scenario: You have an array of employee objects containing id, name, department, salary, and active (boolean).
A. Use filter() to create a list of only 'active' employees.
B. Use map() on the filtered list to increase the salary of employees in the 'Engineering' department by 10%.
C. Use reduce() to calculate the total monthly salary cost of these active engineering employees.
D. Use the Spread Operator to create a deep copy of the final array before logging it, ensuring the original array remains untouched.
E. Log the final total cost.
// rawData.js
const employees = [
{ id: 1, name: 'Alice', department: 'Engineering', salary: 5000, active: true },
{ id: 2, name: 'Bob', department: 'HR', salary: 4000, active: true },
{ id: 3, name: 'Charlie', department: 'Engineering', salary: 5500, active: false },
{ id: 4, name: 'David', department: 'Engineering', salary: 4800, active: true },
];
// Solution Script
const processPayroll = (data) => {
// A. Filter active employees
const activeEmployees = data.filter(emp => emp.active);
// B. Map to update salary for Engineering (Immutable approach)
const updatedSalaries = activeEmployees.map(emp => {
if (emp.department === 'Engineering') {
// Using spread operator to create new object copy
return { ...emp, salary: emp.salary * 1.10 };
}
return emp;
});
// C. Reduce to get total cost of ONLY active Engineers
const totalEngineeringCost = updatedSalaries
.filter(emp => emp.department === 'Engineering')
.reduce((acc, curr) => acc + curr.salary, 0);
// D. Spread operator for deep copy (simplified level)
const finalReport = [...updatedSalaries];
console.log("Updated List:", finalReport);
console.log("Total Engineering Cost:", totalEngineeringCost);
};
processPayroll(employees);
(Unit I - React Basics: JSX vs. createElement)
Understand the React compilation process by creating the same UI twice.
Scenario: You need to render a 'User Profile Card' that contains an <h1> for the name, an <h3> for the job title, and a <button> with an onClick event.
A. Create a React Functional Component named ProfileJSX that implements this using standard JSX syntax.
B. Create a React Functional Component named ProfileRaw that implements the EXACT same structure using React.createElement() without any JSX.
C. The button in both must alert the user's name when clicked.
D. Render both components side-by-side in a parent component.
import React from 'react';
// A. JSX Implementation
const ProfileJSX = ({ name, job }) => {
return (
<div className="card">
<h1>{name}</h1>
<h3>{job}</h3>
<button onClick={() => alert(`Hello ${name}`)}>Contact Me</button>
</div>
);
};
// B. React.createElement Implementation
const ProfileRaw = ({ name, job }) => {
return React.createElement(
'div',
{ className: 'card' },
React.createElement('h1', null, name),
React.createElement('h3', null, job),
React.createElement(
'button',
{ onClick: () => alert(`Hello ${name}`) },
'Contact Me'
)
);
};
// D. Parent Component
export default function App() {
return (
<div>
<h2>JSX Version:</h2>
<ProfileJSX name="John Doe" job="Developer" />
<hr />
<h2>createElement Version:</h2>
<ProfileRaw name="Jane Doe" job="Designer" />
</div>
);
}
(Unit II - Components: Class vs Functional)
Refactor a legacy Class component into a modern Functional component.
Scenario: You have a Class Component ProductCard that:
- Accepts
productNameandpriceas props. - Maintains a state
qtyinitialized to 0. - Has a method
handleBuy()that incrementsqty.
A. Write the code for the original Class Component.
B. Rewrite this component entirely as a Functional Component using the useState hook.
C. Ensure props destructuring is used in the functional version.
import React, { Component, useState } from 'react';
// A. Class Component Implementation
class ProductCardClass extends Component {
constructor(props) {
super(props);
this.state = {
qty: 0
};
}
handleBuy = () => {
this.setState({ qty: this.state.qty + 1 });
};
render() {
const { productName, price } = this.props;
return (
<div style={{ border: '1px solid black', padding: '10px', margin: '10px' }}>
<h3>{productName} (Class)</h3>
<p>Price: ${price}</p>
<p>Quantity: {this.state.qty}</p>
<button onClick={this.handleBuy}>Buy</button>
</div>
);
}
}
// B. Functional Component Implementation
const ProductCardFunc = ({ productName, price }) => {
// Using useState hook
const [qty, setQty] = useState(0);
const handleBuy = () => {
setQty(prevQty => prevQty + 1);
};
return (
<div style={{ border: '1px solid blue', padding: '10px', margin: '10px' }}>
<h3>{productName} (Func)</h3>
<p>Price: ${price}</p>
<p>Quantity: {qty}</p>
<button onClick={handleBuy}>Buy</button>
</div>
);
};
export { ProductCardClass, ProductCardFunc };
(Unit II - Styling in React)
Demonstrate three different styling strategies within a single application.
Scenario: Create a Dashboard component with three distinct widgets.
A. Widget 1: Styled using Inline Styling. Background should be light gray, text color blue.
B. Widget 2: Styled using CSS Modules. Create a Widget.module.css file and apply a class .card with a border-radius and box-shadow.
C. Widget 3: Styled using TailwindCSS. Use utility classes to create a red button with white text, rounded corners, and hover effects (bg-red-500, hover:bg-red-700, etc.).
Note: Assume Tailwind is already configured in the environment.
// Widget.module.css (Assume this file exists)
/*
.card {
border: 1px solid #ccc;
border-radius: 8px;
box-shadow: 2px 2px 5px rgba(0,0,0,0.1);
padding: 20px;
}
*/
import React from 'react';
import styles from './Widget.module.css'; // Importing CSS Module
export default function Dashboard() {
// A. Inline Styling
const inlineStyle = {
backgroundColor: '#f0f0f0',
color: 'blue',
padding: '20px',
marginBottom: '10px'
};
return (
<div>
{/* Widget 1: Inline */}
<div style={inlineStyle}>
<h2>Inline Widget</h2>
<p>This is styled using a JS object.</p>
</div>
{/* Widget 2: CSS Modules */}
<div className={styles.card}>
<h2>Module Widget</h2>
<p>This is styled using CSS Modules to scope styles locally.</p>
</div>
{/* Widget 3: TailwindCSS */}
<div className="p-6 max-w-sm mx-auto bg-white rounded-xl shadow-md space-y-4">
<h2 className="text-xl font-medium text-black">Tailwind Widget</h2>
<button className="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded">
Click Me
</button>
</div>
</div>
);
}
(Unit III - Lifecycle Methods: Class Components)
Implement component lifecycle methods to handle external resources.
Scenario: Create a Class Component named DigitalClock.
A. Initialize state with the current date/time.
B. Use componentDidMount to set up a setInterval that updates the state every second.
C. Use componentDidUpdate to log "Tick" to the console every time the state changes.
D. Use componentWillUnmount to clear the interval to prevent memory leaks when the component is removed from the DOM.
E. Render the current time in HH:MM:SS format.
import React, { Component } from 'react';
class DigitalClock extends Component {
constructor(props) {
super(props);
this.state = {
time: new Date()
};
this.timerID = null;
}
// B. Setup interval on mount
componentDidMount() {
this.timerID = setInterval(() => {
this.tick();
}, 1000);
console.log('Clock Mounted');
}
// C. Log on update
componentDidUpdate(prevProps, prevState) {
if (prevState.time !== this.state.time) {
console.log('Tick');
}
}
// D. Cleanup on unmount
componentWillUnmount() {
clearInterval(this.timerID);
console.log('Clock Unmounted - Timer Cleared');
}
tick() {
this.setState({
time: new Date()
});
}
render() {
return (
<div>
<h2>Current Time:</h2>
{/* E. Render formatted time */}
<h1>{this.state.time.toLocaleTimeString()}</h1>
</div>
);
}
}
export default DigitalClock;
(Unit III - Hooks: useState & useEffect)
Create a functional component that synchronizes the document title with a counter.
Scenario: Create a component DocumentTitleUpdater.
A. Use useState to create a counter variable count initialized to 0.
B. Create buttons to Increment and Decrement the counter.
C. Use useEffect to update the HTML Document Title (browser tab name) to "Count: [X]" whenever the count changes.
D. Add a console log inside the useEffect that says "Title Updated". Ensure this log ONLY appears when count actually changes (use the dependency array correctly).
import React, { useState, useEffect } from 'react';
const DocumentTitleUpdater = () => {
// A. useState for counter
const [count, setCount] = useState(0);
// C. & D. useEffect for side effect (updating document title)
useEffect(() => {
document.title = `Count: ${count}`;
console.log("Title Updated");
// Optional cleanup (not strictly asked but good practice)
return () => {
document.title = "React App";
};
}, [count]); // Dependency array ensures this runs only when 'count' changes
return (
<div style={{ textAlign: 'center', marginTop: '50px' }}>
<h1>Counter Value: {count}</h1>
{/* B. Buttons */}
<button onClick={() => setCount(prev => prev + 1)}>Increment</button>
<button onClick={() => setCount(prev => prev - 1)}>Decrement</button>
</div>
);
};
export default DocumentTitleUpdater;
(Unit III - Hooks: useReducer)
Manage complex state logic using useReducer instead of useState.
Scenario: Create a ShoppingCart component.
A. Define an initial state object { cart: [], total: 0 }.
B. Create a reducer function that handles two action types: 'ADD_ITEM' (adds a product object to the array) and 'CLEAR_CART' (resets the array).
C. The 'ADD_ITEM' action should also accept a payload (price) and update the total.
D. Render the number of items and the total price, along with buttons to dispatch these actions.
import React, { useReducer } from 'react';
// A. Initial State
const initialState = {
cart: [],
total: 0
};
// B. & C. Reducer Function
const cartReducer = (state, action) => {
switch (action.type) {
case 'ADD_ITEM':
return {
...state,
cart: [...state.cart, action.payload],
total: state.total + action.payload.price
};
case 'CLEAR_CART':
return initialState;
default:
return state;
}
};
const ShoppingCart = () => {
const [state, dispatch] = useReducer(cartReducer, initialState);
const addItem = () => {
const newItem = { id: Date.now(), name: 'Product', price: 100 };
dispatch({ type: 'ADD_ITEM', payload: newItem });
};
return (
<div>
<h2>Shopping Cart</h2>
<p>Items in cart: {state.cart.length}</p>
<p>Total Price: ${state.total}</p>
{/* D. Buttons dispatching actions */}
<button onClick={addItem}>Add Item ($100)</button>
<button onClick={() => dispatch({ type: 'CLEAR_CART' })}>
Clear Cart
</button>
<ul>
{state.cart.map(item => (
<li key={item.id}>{item.name} - ${item.price}</li>
))}
</ul>
</div>
);
};
export default ShoppingCart;
(Unit III - Hooks: useContext)
Implement a global Theme system using React Context.
Scenario: You need to toggle between 'light' and 'dark' mode across an app without passing props manually through every level.
A. Create a ThemeContext.
B. Create a ThemeProvider component that holds the theme state ('light' or 'dark') and a function to toggle it.
C. Create a child component ThemedButton that consumes the context via useContext. It should change its background color based on the current theme value.
D. Wrap the App in the Provider.
import React, { createContext, useState, useContext } from 'react';
// A. Create Context
const ThemeContext = createContext();
// B. Create Provider
const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme((prev) => (prev === 'light' ? 'dark' : 'light'));
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
// C. Consuming Component
const ThemedButton = () => {
const { theme, toggleTheme } = useContext(ThemeContext);
const styles = {
backgroundColor: theme === 'light' ? '#fff' : '#333',
color: theme === 'light' ? '#000' : '#fff',
padding: '10px 20px',
border: '1px solid gray',
cursor: 'pointer'
};
return (
<button style={styles} onClick={toggleTheme}>
Switch to {theme === 'light' ? 'Dark' : 'Light'} Mode
</button>
);
};
// D. Main App
export default function App() {
return (
<ThemeProvider>
<div style={{ padding: '50px' }}>
<h1>Context API Example</h1>
<ThemedButton />
</div>
</ThemeProvider>
);
}
(Unit III - Hooks: useCallback & Memoization)
Optimize a component to prevent unnecessary re-renders.
Scenario: You have a parent component with a count state and a text input state.
A. Create a Child component ButtonComponent that accepts handleClick (function) and label (string) as props. Use React.memo to wrap this child.
B. In the Parent, use useState for age and salary.
C. Create increment functions for age and salary.
D. Use useCallback to memoize these increment functions so that changing age does NOT cause the salary button to re-render (and vice-versa).
E. Demonstrate that without useCallback, the child buttons would re-render on every state change.
import React, { useState, useCallback } from 'react';
// A. Child Component wrapped in React.memo
const ButtonComponent = React.memo(({ handleClick, label }) => {
console.log(`Rendering button - ${label}`);
return <button onClick={handleClick}>{label}</button>;
});
const OptimizationDemo = () => {
const [age, setAge] = useState(25);
const [salary, setSalary] = useState(50000);
// D. useCallback ensures this function instance is preserved unless age changes
const incrementAge = useCallback(() => {
setAge((prev) => prev + 1);
}, [age]);
// useCallback ensures this function instance is preserved unless salary changes
const incrementSalary = useCallback(() => {
setSalary((prev) => prev + 1000);
}, [salary]);
return (
<div>
<h2>Age: {age}</h2>
<h2>Salary: {salary}</h2>
{/* Changing Age won't re-render Salary button and vice versa */}
<ButtonComponent handleClick={incrementAge} label="Increment Age" />
<ButtonComponent handleClick={incrementSalary} label="Increment Salary" />
</div>
);
};
export default OptimizationDemo;
(Unit III - Custom Hooks)
Extract logic into a reusable Custom Hook.
Scenario: You frequently need to fetch data from different URLs.
A. Create a custom hook named useFetch(url).
B. Inside the hook, use useState to manage data, loading, and error states.
C. Use useEffect to perform the fetch operation when the url changes.
D. Return the { data, loading, error } object from the hook.
E. Demonstrate its usage in a component UserList that fetches from https://jsonplaceholder.typicode.com/users.
import React, { useState, useEffect } from 'react';
// A. Custom Hook Definition
const useFetch = (url) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error('Network response was not ok');
}
const result = await response.json();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]); // C. Re-run if URL changes
// D. Return state object
return { data, loading, error };
};
// E. Usage in Component
const UserList = () => {
const { data, loading, error } = useFetch('https://jsonplaceholder.typicode.com/users');
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<ul>
{data && data.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
};
export default UserList;
(Unit IV - Working with Forms: Controlled Components)
Create a 'Controlled' Registration Form with validation.
Scenario: Create a form with fields: Username, Email, and Password.
A. Store form values in a single state object formData.
B. Handle input changes using a single handleChange function.
C. Implement validation on Submit:
- Username must be > 3 chars.
- Password must be > 6 chars.
D. If validation fails, display error messages in red below the respective input.
E. If validation passes, alert the form data JSON.
import React, { useState } from 'react';
const RegistrationForm = () => {
// A. State for form data
const [formData, setFormData] = useState({
username: '',
email: '',
password: ''
});
const [errors, setErrors] = useState({});
// B. Handle Changes
const handleChange = (e) => {
const { name, value } = e.target;
setFormData({ ...formData, [name]: value });
};
const validate = () => {
let tempErrors = {};
if (formData.username.length <= 3) tempErrors.username = "Username must be > 3 chars";
if (formData.password.length <= 6) tempErrors.password = "Password must be > 6 chars";
if (!formData.email.includes('@')) tempErrors.email = "Invalid Email";
return tempErrors;
};
const handleSubmit = (e) => {
e.preventDefault();
// C. Validation Check
const validationErrors = validate();
if (Object.keys(validationErrors).length > 0) {
setErrors(validationErrors);
} else {
setErrors({});
// E. Success
alert(JSON.stringify(formData));
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<input name="username" placeholder="Username" onChange={handleChange} />
{/* D. Display Errors */}
{errors.username && <p style={{color:'red'}}>{errors.username}</p>}
</div>
<div>
<input name="email" placeholder="Email" onChange={handleChange} />
{errors.email && <p style={{color:'red'}}>{errors.email}</p>}
</div>
<div>
<input type="password" name="password" placeholder="Password" onChange={handleChange} />
{errors.password && <p style={{color:'red'}}>{errors.password}</p>}
</div>
<button type="submit">Register</button>
</form>
);
};
export default RegistrationForm;
(Unit IV - Working with Forms: Uncontrolled Components)
Use useRef to handle form data in an Uncontrolled Component way.
Scenario: You are building a simple 'Quick Subscribe' form in a footer where re-rendering on every keystroke (controlled component) is unnecessary.
A. Create an input for email.
B. Use useRef to create a reference to the email input DOM element.
C. Handle the form submit event.
D. Inside the submit handler, access the current value of the input directly via the ref (ref.current.value) and log it.
E. Reset the input field using the DOM API after submission.
import React, { useRef } from 'react';
const UncontrolledSubscribe = () => {
// B. Create Ref
const emailInputRef = useRef(null);
// C. Handle Submit
const handleSubmit = (e) => {
e.preventDefault();
// D. Access value via Ref
const emailValue = emailInputRef.current.value;
if(emailValue === '') {
alert("Please enter an email");
return;
}
console.log("Subscribed with:", emailValue);
alert(`Subscribed: ${emailValue}`);
// E. Reset manually
emailInputRef.current.value = '';
};
return (
<form onSubmit={handleSubmit} style={{ marginTop: '20px' }}>
<label>Subscribe to Newsletter: </label>
{/* A. Assign Ref */}
<input type="email" ref={emailInputRef} placeholder="Enter email..." />
<button type="submit">Submit</button>
</form>
);
};
export default UncontrolledSubscribe;
(Unit V - Routing: Setup & Navigation)
Set up a basic React Router configuration.
Scenario: You are building a multipage portfolio site.
A. Define three components: Home, About, and Contact.
B. Configure BrowserRouter (usually in main.jsx or App.jsx) and set up Routes.
C. Create a Navbar component that is visible on all pages.
D. Use the Link or NavLink component in the Navbar for navigation.
E. Use NavLink to apply a class .active (styled with bold text) to the link corresponding to the currently active route.
import React from 'react';
import { BrowserRouter, Routes, Route, NavLink } from 'react-router-dom';
import './App.css'; // Assume .active { font-weight: bold; color: red; } exists
// A. Components
const Home = () => <h2>Home Page</h2>;
const About = () => <h2>About Us</h2>;
const Contact = () => <h2>Contact Us</h2>;
// C. Navbar with NavLink
const Navbar = () => {
return (
<nav>
{/* D. & E. NavLink with active styling support */}
<NavLink to="/" className={({ isActive }) => (isActive ? 'active' : '')}>Home</NavLink>
{' | '}
<NavLink to="/about" className={({ isActive }) => (isActive ? 'active' : '')}>About</NavLink>
{' | '}
<NavLink to="/contact" className={({ isActive }) => (isActive ? 'active' : '')}>Contact</NavLink>
</nav>
);
};
export default function App() {
return (
// B. Router Setup
<BrowserRouter>
<Navbar />
<hr />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</BrowserRouter>
);
}
(Unit V - Routing: Query Params & Dynamic Routes)
Pass data between pages using URL Parameters.
Scenario: You have a list of products. Clicking a product should take you to a details page for that specific product ID.
A. Create a ProductList component that links to /product/1, /product/2, etc.
B. Configure a dynamic route in your App: /product/:id mapping to a ProductDetail component.
C. Inside ProductDetail, use the useParams hook to extract the id from the URL.
D. Display "Viewing details for Product ID: [id]" on the screen.
import React from 'react';
import { BrowserRouter, Routes, Route, Link, useParams } from 'react-router-dom';
// A. Product List Component
const ProductList = () => {
const products = [
{ id: 101, name: 'Laptop' },
{ id: 102, name: 'Phone' },
{ id: 103, name: 'Tablet' },
];
return (
<div>
<h2>Product List</h2>
<ul>
{products.map((p) => (
<li key={p.id}>
<Link to={`/product/${p.id}`}>{p.name}</Link>
</li>
))}
</ul>
</div>
);
};
// C. Product Detail Component using useParams
const ProductDetail = () => {
const { id } = useParams();
return (
<div>
<h2>Product Details</h2>
{/* D. Display ID */}
<p>Viewing details for Product ID: {id}</p>
<Link to="/">Back to List</Link>
</div>
);
};
export default function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<ProductList />} />
{/* B. Dynamic Route Configuration */}
<Route path="/product/:id" element={<ProductDetail />} />
</Routes>
</BrowserRouter>
);
}
(Unit V - HTTP Methods: GET Requests with Axios)
Fetch and Display data using Axios.
Scenario: Create a component PostList.
A. Install/Import axios.
B. Use useEffect to trigger a GET request to https://jsonplaceholder.typicode.com/posts on component mount.
C. Store the response data in a state variable posts.
D. Handle loading state (display "Loading..." while fetching) and error state (display "Failed to fetch" if request fails).
E. Render the first 5 posts in a list.
import React, { useState, useEffect } from 'react';
import axios from 'axios'; // A. Import Axios
const PostList = () => {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
useEffect(() => {
// B. Trigger GET request
axios.get('https://jsonplaceholder.typicode.com/posts')
.then(response => {
// C. Store data
setPosts(response.data.slice(0, 5)); // Keep only first 5
setLoading(false);
})
.catch(err => {
// D. Handle Error
setError('Failed to fetch data');
setLoading(false);
});
}, []);
if (loading) return <p>Loading...</p>;
if (error) return <p style={{color:'red'}}>{error}</p>;
return (
<div>
<h2>Latest Posts</h2>
<ul>
{/* E. Render List */}
{posts.map(post => (
<li key={post.id}>
<strong>{post.title}</strong>
<p>{post.body}</p>
</li>
))}
</ul>
</div>
);
};
export default PostList;
(Unit V - HTTP Methods: POST & DELETE)
Implement Data Persistence logic.
Scenario: You have a comment section.
A. Create a function addComment that sends a POST request to an API endpoint (simulated) with a { text: 'sample', author: 'User' } body.
B. Create a function deleteComment(id) that sends a DELETE request to /comments/:id.
C. Use fetch() API for this task (not Axios).
D. Log the server response or "Deleted successfully" upon completion.
E. Note: Since this is practice, you can write the functions assuming the API exists.
import React, { useState } from 'react';
const CommentManager = () => {
const [status, setStatus] = useState('');
// A. POST Request
const addComment = async () => {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/comments', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ body: 'This is a new comment', postId: 1 }),
});
const data = await response.json();
setStatus(`Added Comment ID: ${data.id}`);
} catch (error) {
setStatus('Error adding comment');
}
};
// B. DELETE Request
const deleteComment = async (id) => {
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/comments/${id}`, {
method: 'DELETE',
});
if (response.ok) {
setStatus(`Deleted Comment ID: ${id}`);
} else {
throw new Error('Delete failed');
}
} catch (error) {
setStatus('Error deleting comment');
}
};
return (
<div>
<h3>API Action Status: {status}</h3>
<button onClick={addComment}>Add Comment (POST)</button>
<button onClick={() => deleteComment(1)}>Delete Comment 1 (DELETE)</button>
</div>
);
};
export default CommentManager;
(Unit VI - Redux Basics: Store & Reducer)
Set up the foundational blocks of a Redux application.
Scenario: You are building a 'Counter' feature using Redux (Standard/Legacy style as per syllabus flow).
A. Define initial state { value: 0 }.
B. Create a counterReducer function that handles action types: 'INCREMENT' and 'DECREMENT'.
C. Create the Redux Store using createStore (import from 'redux').
D. Write a script to Subscribe to the store changes and log the state.
E. Dispatch an increment action twice and a decrement action once manually.
// Note: This often runs in a standalone JS file or setup file
import { createStore } from 'redux';
// A. Initial State
const initialState = { value: 0 };
// B. Reducer
const counterReducer = (state = initialState, action) => {
switch (action.type) {
case 'INCREMENT':
return { value: state.value + 1 };
case 'DECREMENT':
return { value: state.value - 1 };
default:
return state;
}
};
// C. Create Store
const store = createStore(counterReducer);
// D. Subscribe
const unsubscribe = store.subscribe(() => {
console.log('Current State:', store.getState());
});
// E. Dispatch Actions
console.log("Dispatching INCREMENT...");
store.dispatch({ type: 'INCREMENT' });
console.log("Dispatching INCREMENT...");
store.dispatch({ type: 'INCREMENT' });
console.log("Dispatching DECREMENT...");
store.dispatch({ type: 'DECREMENT' });
// Clean up
unsubscribe();
(Unit VI - Redux: Connecting Components)
Connect a React component to the Redux store.
Scenario: You have the store from the previous question. Now you need to provide it to a React app.
A. Wrap your main App component with the <Provider> from react-redux and pass the store.
B. Create a CounterDisplay component that uses useSelector to read the counter value.
C. Create a CounterControls component that uses useDispatch to dispatch 'INCREMENT' and 'DECREMENT' actions.
D. Render both components in the App.
import React from 'react';
import { createStore } from 'redux';
import { Provider, useSelector, useDispatch } from 'react-redux';
// Reducer Setup (same as before)
const reducer = (state = { value: 0 }, action) => {
if (action.type === 'INCREMENT') return { value: state.value + 1 };
if (action.type === 'DECREMENT') return { value: state.value - 1 };
return state;
};
const store = createStore(reducer);
// B. Component Reading State
const CounterDisplay = () => {
const count = useSelector(state => state.value);
return <h1>Count: {count}</h1>;
};
// C. Component Dispatching Actions
const CounterControls = () => {
const dispatch = useDispatch();
return (
<div>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
<button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button>
</div>
);
};
// A. & D. Provider and Render
export default function App() {
return (
<Provider store={store}>
<div style={{ textAlign: 'center' }}>
<CounterDisplay />
<CounterControls />
</div>
</Provider>
);
}
(Unit VI - Debugging & Deployment)
Simulate the preparation for deployment and basic debugging check.
Scenario: You have finished your React app my-app created with create-react-app.
A. Write the command to create an optimized production build.
B. Explain what happens to the code during this build process (Minification, Bundling).
C. Write a simple Error Boundary component ErrorBoundary.
- It should have
componentDidCatchto log errors. - It should have
getDerivedStateFromErrorto update state{ hasError: true }. - It should render "Something went wrong." if an error is caught.
D. Wrap a dummyBuggyComponentin the ErrorBoundary.
// A. Command: npm run build
/*
B. Explanation:
- Bundling: Combines multiple JS/CSS files into fewer files to reduce HTTP requests.
- Minification: Removes whitespace, comments, and renames variables to shorten names, reducing file size.
- Transpilation: Converts modern ES6+ code to browser-compatible ES5.
*/
import React from 'react';
// C. Error Boundary Component
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error("Error caught by boundary:", error, errorInfo);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
// D. Usage
const BuggyComponent = () => {
throw new Error("I crashed!"); // Simulating a crash
return <div>Safe</div>;
};
export default function App() {
return (
<ErrorBoundary>
<BuggyComponent />
</ErrorBoundary>
);
}
(Comprehensive Integration - Units III, V, VI)
Create a 'Login' flow integrating State, API, and Redux.
Scenario:
- User enters username/password in a form (State).
- On Submit, send POST request to
/api/login(HTTP/Fetch). - If successful, the API returns a
tokenanduserobject. - Dispatch a
'LOGIN_SUCCESS'action to Redux to store this user data (Redux).
A. Create the Redux Action Creator loginSuccess(user).
B. Create the LoginComponent that handles the form state, the fetch logic, and dispatches the action upon success.
import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
// A. Action Creator
const loginSuccess = (user) => ({
type: 'LOGIN_SUCCESS',
payload: user
});
// B. Login Component
const LoginComponent = () => {
const [creds, setCreds] = useState({ username: '', password: '' });
const dispatch = useDispatch();
const handleChange = (e) => {
setCreds({ ...creds, [e.target.name]: e.target.value });
};
const handleLogin = async (e) => {
e.preventDefault();
try {
// Simulated API Call
const response = await fetch('https://jsonplaceholder.typicode.com/users/1', {
method: 'GET' // Using GET to mock a successful user fetch
});
if(response.ok) {
const user = await response.json();
// Dispatch to Redux
dispatch(loginSuccess(user));
alert("Login Successful! User stored in Redux.");
} else {
alert("Login Failed");
}
} catch (err) {
console.error(err);
}
};
return (
<form onSubmit={handleLogin}>
<input name="username" onChange={handleChange} placeholder="Username" />
<input name="password" type="password" onChange={handleChange} placeholder="Password" />
<button type="submit">Login</button>
</form>
);
};
export default LoginComponent;
(Unit I - JavaScript Refresher: Destructuring & Spread)
Create a utility script to handle configuration merging.
Scenario: You are writing a library that accepts user configurations but needs to fall back to defaults.
A. Define a default configuration object defaultConfig with { theme: 'light', timeout: 5000, retries: 3 }.
B. Create a function setupApp(userConfig).
C. Inside the function, use the Spread Operator to merge userConfig into defaultConfig (user settings override defaults).
D. Use Object Destructuring to extract theme and timeout from the merged object.
E. Log a message: "App starting with [theme] theme and [timeout]ms timeout."
// A. Default Configuration
const defaultConfig = {
theme: 'light',
timeout: 5000,
retries: 3
};
// B. Setup Function
const setupApp = (userConfig) => {
// C. Merge configurations (Spread Operator)
// userConfig comes last to override defaults
const finalConfig = { ...defaultConfig, ...userConfig };
// D. Destructuring
const { theme, timeout } = finalConfig;
// E. Logging
console.log(`App starting with ${theme} theme and ${timeout}ms timeout.`);
// Return for verification
return finalConfig;
};
// Testing the script
setupApp({ theme: 'dark' }); // Should keep timeout 5000
setupApp({ timeout: 10000, retries: 5 }); // Should keep theme 'light'
(Unit I - JS Modules)
Simulate a Modular Application structure.
Scenario: You need to separate utility logic from the main application.
A. Create a conceptual file mathUtils.js.
B. Inside it, write two functions: add(a, b) and subtract(a, b).
C. Use Named Exports for these functions.
D. Create a default export function logResult(value) in the same file.
E. Write a conceptual app.js that imports add (named) and logger (default, renamed from logResult) and uses them.
// --- mathUtils.js ---
// B. & C. Named Exports
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
// D. Default Export
const logResult = (value) => {
console.log("Result:", value);
};
export default logResult;
// --- app.js ---
// E. Imports (Default import can be named anything, e.g., 'logger')
import logger, { add } from './mathUtils.js';
const runCalculation = () => {
const sum = add(10, 5);
logger(sum); // Outputs: Result: 15
};
runCalculation();
(Unit II - Components: Lifting State Up)
Share state between two sibling components by moving it to their parent.
Scenario: Create a temperature converter.
A. Create a parent component TemperatureCalculator that holds the state temperature (in Celsius).
B. Create a child component CelsiusInput that accepts temperature and a handler onChange as props.
C. Create a second child component FahrenheitDisplay that accepts celsius as a prop and displays the converted value (F = C * 9/5 + 32).
D. Ensuring that typing in CelsiusInput updates the parent state, which immediately updates FahrenheitDisplay.
import React, { useState } from 'react';
// B. Child 1: Input
const CelsiusInput = ({ temperature, onTemperatureChange }) => {
return (
<fieldset>
<legend>Enter Celsius:</legend>
<input
value={temperature}
onChange={(e) => onTemperatureChange(e.target.value)}
type="number"
/>
</fieldset>
);
};
// C. Child 2: Display
const FahrenheitDisplay = ({ celsius }) => {
const fahrenheit = (parseFloat(celsius || 0) * 9) / 5 + 32;
return <p>Fahrenheit: {fahrenheit}</p>;
};
// A. Parent Component
const TemperatureCalculator = () => {
const [temp, setTemp] = useState('');
const handleWebChange = (val) => {
setTemp(val);
};
return (
<div>
{/* D. Passing props down */}
<CelsiusInput temperature={temp} onTemperatureChange={handleWebChange} />
<FahrenheitDisplay celsius={temp} />
</div>
);
};
export default TemperatureCalculator;
(Unit II - Rendering Elements: Keys & Lists)
Demonstrate the importance of the key prop in React lists.
Scenario: Render a dynamic list of Todo items.
A. Initialize a state with an array of objects: [{id: 1, text: 'Task A'}, {id: 2, text: 'Task B'}].
B. Render these items in a <ul>. Each <li> must include the text and a "Delete" button.
C. Crucial: Assign the key prop using the item's unique id.
D. Implement the delete functionality.
E. Add a comment in the code explaining why using index as a key is bad practice for lists that can be reordered or filtered.
import React, { useState } from 'react';
const TodoList = () => {
// A. Initial State
const [todos, setTodos] = useState([
{ id: 101, text: 'Learn React' },
{ id: 102, text: 'Practice Coding' },
{ id: 103, text: 'Build App' }
]);
// D. Delete Functionality
const handleDelete = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
return (
<ul>
{todos.map((todo) => (
/*
C. Key Usage:
Using todo.id instead of index.
E. Why not index?
If the list is reordered or items are deleted, the index changes.
React uses keys to identify which items have changed, added, or removed.
Using index can lead to state bugs and performance issues in dynamic lists.
*/
<li key={todo.id}>
{todo.text}
<button onClick={() => handleDelete(todo.id)}>Delete</button>
</li>
))}
</ul>
);
};
export default TodoList;
(Unit II - Conditional Rendering)
Implement multiple conditional rendering techniques.
Scenario: create a UserDashboard component.
A. Accept a prop isLoggedIn (boolean) and username (string).
B. Use the Ternary Operator to render a "Welcome, [username]" message if logged in, otherwise render a "Please Login" button.
C. Use the Logical && (Short Circuit) operator to render a specific AdminPanel component ONLY if username is exactly "admin".
D. Use a standard If/Else statement (inside the function body, before return) to return null (render nothing) if the isLoading prop is true.
import React from 'react';
const AdminPanel = () => <div style={{background: 'red'}}>Admin Settings</div>;
const UserDashboard = ({ isLoggedIn, username, isLoading }) => {
// D. If/Else for early return
if (isLoading) {
return null; // Or <p>Loading...</p>
}
return (
<div>
{/* B. Ternary Operator */}
{isLoggedIn ? (
<h1>Welcome, {username}!</h1>
) : (
<button>Please Login</button>
)}
{/* C. Logical && Short Circuit */}
{isLoggedIn && username === 'admin' && <AdminPanel />}
</div>
);
};
export default UserDashboard;
(Unit III - Events: Synthetic Events)
Handle events and prevent default browser behavior.
Scenario: You are building a custom Context Menu.
A. Create a div area with dimensions 300x300px and a background color.
B. Attach an onContextMenu event handler to this div.
C. Inside the handler, use e.preventDefault() to stop the browser's default right-click menu from appearing.
D. Instead, log the mouse coordinates (e.clientX, e.clientY) to the console.
E. Add a button that logs "Clicked" using onClick, and demonstrate e.stopPropagation() by placing this button inside the div and ensuring the div's click handler doesn't trigger when the button is clicked.
import React from 'react';
const CustomEvents = () => {
const handleRightClick = (e) => {
// C. Prevent Default Context Menu
e.preventDefault();
// D. Log Coordinates
console.log(`Right click at: X=${e.clientX}, Y=${e.clientY}`);
};
const handleDivClick = () => {
console.log("Div Clicked");
};
const handleButtonClick = (e) => {
// E. Stop Propagation
e.stopPropagation();
console.log("Button Clicked (Propagation Stopped)");
};
return (
<div
// A. Dimensions and Background
style={{ width: '300px', height: '300px', background: 'lightgray' }}
// B. Event Handlers
onContextMenu={handleRightClick}
onClick={handleDivClick}
>
<p>Right click inside me. Click button below.</p>
<button onClick={handleButtonClick}>Click Me</button>
</div>
);
};
export default CustomEvents;
(Unit III - Hooks: useRef)
Use useRef to store mutable values that do not trigger re-renders.
Scenario: Create a Stopwatch component.
A. Create a state variable time (initially 0) to display the seconds.
B. Create a useRef variable named timerRef to store the interval ID. (We use ref because we don't need to re-render when the ID changes).
C. Implement a startTimer function that sets an interval increasing time by 1 every second, storing the ID in timerRef.current.
D. Implement a stopTimer function that calls clearInterval using timerRef.current.
E. Ensure that clicking Start multiple times doesn't create multiple intervals.
import React, { useState, useRef } from 'react';
const Stopwatch = () => {
// A. State for display
const [time, setTime] = useState(0);
// B. Ref for Interval ID
const timerRef = useRef(null);
// C. Start Function
const startTimer = () => {
// E. Prevent multiple intervals
if (timerRef.current !== null) return;
timerRef.current = setInterval(() => {
setTime(prev => prev + 1);
}, 1000);
};
// D. Stop Function
const stopTimer = () => {
if (timerRef.current) {
clearInterval(timerRef.current);
timerRef.current = null;
}
};
return (
<div>
<h1>Seconds: {time}</h1>
<button onClick={startTimer}>Start</button>
<button onClick={stopTimer}>Stop</button>
</div>
);
};
export default Stopwatch;
(Unit III - Hooks: useMemo)
Optimize performance for expensive calculations.
Scenario: You have a list of numbers and a filter.
A. Create a component with a state count (for a forced re-render button) and a static array of large numbers.
B. Create a function expensiveCalculation(num) that simply returns num * 2 but logs "Calculating..." to the console.
C. Use useMemo to cache the result of mapping this calculation over the array.
D. The useMemo should only depend on the numbers array.
E. Add a button that increments count. Verify in the console that "Calculating..." does NOT appear when clicking this button (proving memoization worked).
import React, { useState, useMemo } from 'react';
const ExpensiveComponent = () => {
const [count, setCount] = useState(0);
const [numbers] = useState([10, 20, 30, 40]);
// B. Expensive function logic inside useMemo
// C. useMemo implementation
const doubledNumbers = useMemo(() => {
return numbers.map(num => {
console.log("Calculating...", num);
return num * 2;
});
}, [numbers]); // D. Dependency: only runs if 'numbers' changes
return (
<div>
<h2>Doubled: {doubledNumbers.join(', ')}</h2>
<p>Counter: {count}</p>
{/* E. Re-render trigger */}
<button onClick={() => setCount(count + 1)}>Re-render Component</button>
</div>
);
};
export default ExpensiveComponent;
(Unit III - Custom Hooks)
Create a hook to manage Local Storage interactions.
Scenario: Create a custom hook useLocalStorage(key, initialValue).
A. Inside the hook, initialize state using a function (lazy initialization) to read from localStorage.getItem(key) or fall back to initialValue.
B. Create a setValue function that updates the React state AND writes the new value to localStorage.setItem.
C. Return [storedValue, setValue] from the hook.
D. Demonstrate usage in a component to store a "username".
import React, { useState } from 'react';
// A. Custom Hook
const useLocalStorage = (key, initialValue) => {
// Lazy State Initialization
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
}
catch (error) {
return initialValue;
}
});
// B. Setter Function
const setValue = (value) => {
try {
setStoredValue(value);
window.localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error(error);
}
};
// C. Return
return [storedValue, setValue];
};
// D. Usage
const StorageDemo = () => {
const [name, setName] = useLocalStorage('username', 'Guest');
return (
<div>
<p>Stored Name: {name}</p>
<input
value={name}
onChange={(e) => setName(e.target.value)}
/>
</div>
);
};
export default StorageDemo;
(Unit IV - Forms: Advanced Inputs)
Handle different input types in a controlled form.
Scenario: Create a PreferencesForm.
A. Initialize state { newsletter: false, role: 'user' }.
B. Create a Checkbox input bound to newsletter. Note: Checkboxes use e.target.checked, not value.
C. Create a Select dropdown bound to role with options: 'User', 'Editor', 'Admin'.
D. Create a generic handleChange function that checks type === 'checkbox' to determine whether to read checked or value.
E. Submit the form and alert the JSON result.
import React, { useState } from 'react';
const PreferencesForm = () => {
// A. State
const [formData, setFormData] = useState({
newsletter: false,
role: 'user'
});
// D. Generic Handler
const handleChange = (e) => {
const { name, value, type, checked } = e.target;
setFormData(prev => ({
...prev,
[name]: type === 'checkbox' ? checked : value
}));
};
const handleSubmit = (e) => {
e.preventDefault();
alert(JSON.stringify(formData));
};
return (
<form onSubmit={handleSubmit}>
{/* B. Checkbox */}
<label>
Subscribe to Newsletter:
<input
name="newsletter"
type="checkbox"
checked={formData.newsletter}
onChange={handleChange}
/>
</label>
<br />
{/* C. Select */}
<label>
Role:
<select name="role" value={formData.role} onChange={handleChange}>
<option value="user">User</option>
<option value="editor">Editor</option>
<option value="admin">Admin</option>
</select>
</label>
<br />
<button type="submit">Save</button>
</form>
);
};
export default PreferencesForm;
(Unit V - Routing: Nested Routes)
Implement a Dashboard structure with nested navigation.
Scenario: A Dashboard page has sub-sections: 'Profile' and 'Settings'.
A. In App.js, create a parent route /dashboard/* pointing to a Dashboard component. (Note the * wildcard if using React Router v6 descendant routes, or define children inside).
B. In the Dashboard component, create a sidebar with Links to profile and settings.
C. Inside the Dashboard component, use the <Outlet /> component (React Router v6) to determine where the child components render.
D. Define the nested routes so that /dashboard/profile renders Profile and /dashboard/settings renders Settings.
import React from 'react';
import { BrowserRouter, Routes, Route, Link, Outlet } from 'react-router-dom';
// Sub-components
const Profile = () => <h3>User Profile</h3>;
const Settings = () => <h3>App Settings</h3>;
// B. Dashboard with Outlet
const Dashboard = () => {
return (
<div style={{ display: 'flex' }}>
<nav style={{ width: '200px', borderRight: '1px solid black' }}>
<ul>
{/* Relative Links */}
<li><Link to="profile">Profile</Link></li>
<li><Link to="settings">Settings</Link></li>
</ul>
</nav>
<div style={{ padding: '20px' }}>
<h1>Dashboard</h1>
{/* C. Outlet for nested content */}
<Outlet />
</div>
</div>
);
};
// A. & D. Route Configuration
export default function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/dashboard" element={<Dashboard />}>
<Route path="profile" element={<Profile />} />
<Route path="settings" element={<Settings />} />
</Route>
</Routes>
</BrowserRouter>
);
}
(Unit V - Routing: Programmatic Navigation)
Navigate the user automatically after an action.
Scenario: Create a Login page.
A. Create a form with a Login button.
B. Import useNavigate from react-router-dom.
C. On form submit, simulate a login check (e.g., if(true)).
D. If successful, use the navigate function to redirect the user to /home.
E. Pass a state object { fromLogin: true } during navigation so the Home page knows where the user came from.
import React from 'react';
import { useNavigate } from 'react-router-dom';
const Login = () => {
// B. Hook
const navigate = useNavigate();
const handleLogin = (e) => {
e.preventDefault();
// C. Simulate Logic
const success = true;
if (success) {
console.log("Login Success, Redirecting...");
// D. & E. Navigate with State
navigate('/home', { state: { fromLogin: true } });
}
};
return (
<form onSubmit={handleLogin}>
<h2>Login Page</h2>
<button type="submit">Log In</button>
</form>
);
};
export default Login;
(Unit V - HTTP: PUT Request)
Update a resource on the server.
Scenario: You have a form to edit a Post.
A. Create a function updatePost(id, newData).
B. Use fetch to send a PUT request to https://jsonplaceholder.typicode.com/posts/[id].
C. The body should contain JSON.stringify(newData) and headers must include 'Content-type': 'application/json'.
D. Handle the promise response and log "Update Successful" with the returned data.
E. Trigger this function with a button click for ID 1 and some dummy text.
import React, { useState } from 'react';
const PostEditor = () => {
const [message, setMessage] = useState('');
// A. Update Function
const updatePost = (id, newData) => {
// B. Fetch PUT
fetch(`https://jsonplaceholder.typicode.com/posts/${id}`, {
method: 'PUT',
body: JSON.stringify(newData),
headers: {
'Content-type': 'application/json; charset=UTF-8',
},
})
.then((response) => response.json())
.then((json) => {
// D. Log success
console.log(json);
setMessage(`Update Successful for ID ${id}`);
});
};
return (
<div>
<h3>{message}</h3>
{/* E. Trigger */}
<button
onClick={() => updatePost(1, { id: 1, title: 'foo', body: 'bar', userId: 1 })}
>
Update Post 1
</button>
</div>
);
};
export default PostEditor;
(Unit VI - Redux: Combine Reducers)
Structure a Redux store with multiple state slices.
Scenario: An e-commerce app has user state and products state.
A. Create userReducer (handles 'SET_USER') and productReducer (handles 'ADD_PRODUCT').
B. Use combineReducers from redux to merge them into a rootReducer.
C. Create the store using this root reducer.
D. Dispatch an action to set the user and another to add a product.
E. Log store.getState() to show the structured state tree: { user: ..., products: ... }.
import { createStore, combineReducers } from 'redux';
// A. Reducers
const userReducer = (state = { name: 'Guest' }, action) => {
switch (action.type) {
case 'SET_USER': return { ...state, name: action.payload };
default: return state;
}
};
const productReducer = (state = { items: [] }, action) => {
switch (action.type) {
case 'ADD_PRODUCT': return { ...state, items: [...state.items, action.payload] };
default: return state;
}
};
// B. Combine Reducers
const rootReducer = combineReducers({
user: userReducer,
products: productReducer
});
// C. Create Store
const store = createStore(rootReducer);
// D. Dispatch Actions
store.dispatch({ type: 'SET_USER', payload: 'John Doe' });
store.dispatch({ type: 'ADD_PRODUCT', payload: 'Laptop' });
// E. Verify Structure
console.log(store.getState());
// Output: { user: { name: 'John Doe' }, products: { items: ['Laptop'] } }
(Unit VI - Redux: Action Types & Creators)
Implement Redux Best Practices.
Scenario: Avoid "Magic Strings" in your Redux setup.
A. Create a file/object ActionTypes that defines constants like LOGIN_REQUEST = 'LOGIN_REQUEST'.
B. Create an Action Creator function requestLogin(credentials) that returns the action object using the constant type.
C. Write a reducer that imports this constant and uses it in the switch case.
D. Explain in a comment why using constants is better than typing strings like 'LOGIN_REQUEST' repeatedly.
// A. Action Constants
const ActionTypes = {
LOGIN_REQUEST: 'LOGIN_REQUEST',
LOGIN_SUCCESS: 'LOGIN_SUCCESS'
};
// B. Action Creator
const requestLogin = (credentials) => {
return {
type: ActionTypes.LOGIN_REQUEST,
payload: credentials
};
};
// C. Reducer using Constants
const authReducer = (state = {}, action) => {
switch (action.type) {
case ActionTypes.LOGIN_REQUEST:
return { loading: true };
case ActionTypes.LOGIN_SUCCESS:
return { loading: false, user: action.payload };
default:
return state;
}
};
/*
D. Why Constants?
1. Prevents typos (e.g., 'LOGIN_REQUST' vs 'LOGIN_REQUEST').
2. Better IDE autocompletion.
3. Easy to refactor/rename action types in one place.
*/
(Unit III - Context + useReducer Pattern)
Implement Global State Management without Redux (common alternative pattern).
Scenario: Create a StoreProvider component.
A. Define initialState and a reducer function.
B. Create a Context StoreContext.
C. In StoreProvider, use useReducer to get state and dispatch.
D. Pass { state, dispatch } into the StoreContext.Provider value.
E. Demonstrate how a child component would consume this to dispatch an action.
import React, { createContext, useReducer, useContext } from 'react';
// A. State & Reducer
const initialState = { count: 0 };
const reducer = (state, action) => {
switch (action.type) {
case 'INC': return { count: state.count + 1 };
default: return state;
}
};
// B. Context
const StoreContext = createContext();
// C. Provider
export const StoreProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
// D. Pass value
return (
<StoreContext.Provider value={{ state, dispatch }}>
{children}
</StoreContext.Provider>
);
};
// E. Child Component
export const Counter = () => {
const { state, dispatch } = useContext(StoreContext);
return (
<button onClick={() => dispatch({ type: 'INC' })}>
Count: {state.count}
</button>
);
};
(Unit IV - Form Validation with Regex)
Validate a specific format in a form.
Scenario: Create a Password setup form.
A. Input field for password.
B. On change, check the value against a Regex: ^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$ (Minimum 8 characters, at least one letter and one number).
C. If the regex test fails, set a state isValid to false and errorMsg to "Password too weak".
D. Disable the submit button if !isValid.
E. Display the error message in red.
import React, { useState } from 'react';
const PasswordForm = () => {
const [password, setPassword] = useState('');
const [errorMsg, setErrorMsg] = useState('');
const [isValid, setIsValid] = useState(false);
const handleChange = (e) => {
const val = e.target.value;
setPassword(val);
// B. Regex Check
const regex = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/;
if (regex.test(val)) {
setIsValid(true);
setErrorMsg('');
} else {
// C. Validation Fail
setIsValid(false);
setErrorMsg('Password too weak (Min 8 chars, 1 letter, 1 number)');
}
};
return (
<form>
<input type="password" value={password} onChange={handleChange} />
{/* E. Display Error */}
{!isValid && <p style={{ color: 'red' }}>{errorMsg}</p>}
{/* D. Disable Button */}
<button disabled={!isValid}>Set Password</button>
</form>
);
};
export default PasswordForm;
(Unit II - CSS Modules & Dynamic Styling)
Apply styles conditionally using CSS Modules.
Scenario: You have a Notification component.
A. Assume a CSS module file exists with classes: .box, .success (green), .error (red).
B. The component receives type ('success' or 'error') and message as props.
C. Construct the className string dynamically. It should always have .box, and append .success or .error based on the prop.
D. Use Template Literals for the string construction: `{styles[type]}`.
/* Notification.module.css (Assumed)
.box { padding: 10px; border: 1px solid black; }
.success { background-color: lightgreen; }
.error { background-color: pink; }
*/
import React from 'react';
import styles from './Notification.module.css';
const Notification = ({ type, message }) => {
// C. & D. Dynamic Class Construction
// Accessing styles.success or styles.error dynamically based on 'type' prop
const assignedClass = `${styles.box} ${styles[type]}`;
return (
<div className={assignedClass}>
{message}
</div>
);
};
// Usage Example: <Notification type="success" message="Done!" />
export default Notification;
(Unit VI - Deployment & Environment Variables)
Prepare an app for handling different API URLs in Dev vs Prod.
Scenario: You are deploying your app.
A. Explain (in comments) what a .env file is used for in React.
B. Create a code snippet that reads REACT_APP_API_URL (or VITE_API_URL depending on bundler) from the environment.
C. Use a logical OR || to provide a fallback localhost URL if the env variable is missing.
D. Use this constant in a fetch call.
/*
A. .env Explanation:
Used to store environment-specific variables (like API keys, URLs).
These are injected at build time. They keep secrets out of code
and allow different configs for Development vs Production.
*/
import React, { useEffect } from 'react';
const DataFetcher = () => {
// B. & C. Read Env Variable with Fallback
// Note: Create React App requires 'REACT_APP_' prefix
const API_BASE = process.env.REACT_APP_API_URL || 'http://localhost:3000';
useEffect(() => {
// D. Use in Fetch
fetch(`${API_BASE}/users`)
.then(res => res.json())
.then(data => console.log(data));
}, []);
return <div>Check Console for Data from {API_BASE}</div>;
};
export default DataFetcher;
(Unit I - JS Classes Refresher)
Demonstrate ES6 Class inheritance, often relevant for understanding legacy React Class Components.
Scenario: Create a generic class Animal and a specific class Dog.
A. Animal has a constructor taking name and a method speak() returning "Noise".
B. Dog extends Animal.
C. Dog constructor must call super(name).
D. Dog overrides speak() to return "Bark".
E. Instantiate a Dog and log its name and speak result.
// A. Base Class
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return "Noise";
}
}
// B. Extended Class
class Dog extends Animal {
constructor(name) {
// C. Super call
super(name);
}
// D. Override method
speak() {
return "Bark";
}
}
// E. Instantiation
const myDog = new Dog("Rex");
console.log(myDog.name); // Rex
console.log(myDog.speak()); // Bark