username and profilePicture as props to the UserProfile component. This allows you to reuse the same UserProfile component for different users by simply providing different values for these props.useState hook allows functional components to have "state" – a way to store and manage data that can change over time and trigger re-renders of the component when it does. This preservation of data between renders is exactly what makes interactive UIs possible.
useState
ex. You have a Product component that displays an item, and you want to add a button to it that, when clicked, increases the quantity of that product in a shopping cart. How would you use useState within the Product component to manage the quantity of a specific item before it's added to the cart?
useState stores the quantity between rendersfunction Product({ product, onAddToCart }) {
// Set up local state for quantity
const [quantity, setQuantity] = useState(0);
// Update quantity on button click
const increaseQuantity = () => {
setQuantity(prev => prev + 1);
};
// Use the quantity when adding to the cart
const handleAddToCart = () => {
onAddToCart(product, quantity);
};
return (
<div>
<h3>{product.name}</h3>
<p>Quantity: {quantity}</p>
<button onClick={increaseQuantity}>+</button>
<button onClick={handleAddToCart}>Add to Cart</button>
</div>
);
}
ex. let's consider what happens when multiple components need to share or react to the same piece of state. For example, if you have a shopping cart component that needs to display the total quantity of all items. How might you initially approach sharing the quantity state from individual Product components with a ShoppingCart component, and what potential issues might arise as your application grows?
Product’s UI quantity locally with useState (if you want a stepper before adding).(productId, quantity) up to a parent component via a callback.CartProvider) as an object/array of items.ShoppingCart.import { useMemo, useState } from "react";
function Product({ product, onAddToCart }) {
// local quantity state for specific Product
const [quantity, setQuantity] = useState(0);
const increaseQuantity = () => setQuantity(q => q + 1);
const decreaseQuantity = () => setQuantity(q => Math.max(0, q - 1));
const handleAddToCart = () => {
onAddToCart(product.id, quantity);
setQuantity(0); // optional: reset after adding
};
return (
<div style={{ border: "1px solid #ddd", padding: 12, marginBottom: 12 }}>
<h3 style={{ margin: 0 }}>{product.name}</h3>
<p style={{ marginTop: 6 }}>${product.price.toFixed(2)}</p>
<div style={{ display: "flex", gap: 8, alignItems: "center" }}>
<button onClick={decreaseQuantity}>-</button>
<span>Qty: {quantity}</span>
<button onClick={increaseQuantity}>+</button>
<button onClick={handleAddToCart} style={{ marginLeft: 12 }}>
Add to Cart
</button>
</div>
</div>
);
}
function ShoppingCart({ cartItems, products }) {
// Derived data, not stored state
// Recomputed only when cartItems changes
const totalQuantity = useMemo(() => {
return cartItems.reduce((sum, item) => sum + item.quantity, 0);
}, [cartItems]);
// Joins cart items with product data
const totalPrice = useMemo(() => {
return cartItems.reduce((sum, item) => {
const p = products.find(x => x.id === item.productId);
return sum + (p ? p.price * item.quantity : 0);
}, 0);
}, [cartItems, products]);
return (
<div style={{ border: "1px solid #aaa", padding: 12 }}>
<h2 style={{ marginTop: 0 }}>Shopping Cart</h2>
<p>Total items: {totalQuantity}</p>
<p>Total price: ${totalPrice.toFixed(2)}</p>
<ul>
{cartItems.map(item => (
<li key={item.productId}>
{item.productId} — Qty: {item.quantity}
</li>
))}
</ul>
</div>
);
}
// Shared by Product and ShoppingCart
export default function App() {
const products = [
{ id: "p1", name: "T-Shirt", price: 20 },
{ id: "p2", name: "Hat", price: 12 },
{ id: "p3", name: "Socks", price: 6 }
];
// lifted state: single source of truth for the cart
const [cartItems, setCartItems] = useState([]); // [{ productId, quantity }]
// Checks if the product already exists in the cart
// If not, adds a new item
// If yes, increases its quantity
// Uses immutable updates so React can detect changes
const handleAddToCart = (productId, quantityToAdd) => {
setCartItems(prev => {
const existing = prev.find(item => item.productId === productId);
if (!existing) {
return [...prev, { productId, quantity: quantityToAdd }];
}
return prev.map(item =>
item.productId === productId
? { ...item, quantity: item.quantity + quantityToAdd }
: item
);
});
};
return (
<div style={{ maxWidth: 520, margin: "20px auto", fontFamily: "system-ui" }}>
<h1>Store</h1>
// Parent passes onAddToCart down
// Products call it when needed
{products.map(p => (
<Product key={p.id} product={p} onAddToCart={handleAddToCart} />
))}
// Cart receives shared state
// Automatically re-renders when cart updates
<ShoppingCart cartItems={cartItems} products={products} />
</div>
);
}
useEffect is about performing actions after rendering. It's about handling "side effects" in functional components. These side effects can include things like data fetching, subscriptions, or manually changing the DOM
useEffect
ex. Imagine you have a component that fetches user data from an API. How would you use useEffect to fetch this data only once when the component mounts, and then re-fetch it if a userId prop changes? What would be the role of the dependency array in this scenario?
You would use useEffect with a dependency array containing userId so the effect runs:
userId changes[]). If you want to re-fetch when userId changes, you'd include userId in that arrayuseEffect allows you to perform cleanup operations before the component unmounts or before the effect re-runs due to dependency changes. This is vital for preventing memory leaks, avoiding setting state on unmounted components, and managing subscriptions or event listeners effectively.import { useEffect, useState } from "react";
// component renders with an initial userId
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
// react runs effect after DOM updates
useEffect(() => {
let isCancelled = false;
const fetchUser = async () => {
setLoading(true);
const res = await fetch(`/api/users/${userId}`);
const data = await res.json();
if (!isCancelled) {
setUser(data);
setLoading(false);
}
};
fetchUser();
return () => {
isCancelled = true;
};
}, [userId]);
if (loading) return <p>Loading...</p>;
if (!user) return <p>No user found</p>;
return <h2>{user.name}</h2>;
}
Layout component that provides a common structure (like a header, sidebar, and footer) for many different pages. How would you use component composition to allow each page to inject its unique content into this Layout component, without the Layout component needing to know anything specific about the page's content?
children so Layout renders a consistent frame (header/sidebar/footer) and each page injects its own content into the “main” area. Layout doesn’t need to know what the page is—only where to place it.props.children, the Layout component acts as a generic container, rendering whatever is passed to it between its opening and closing tags.TypeScript