Problem Statement
Design and implement a "Story" feature similar to Instagram or WhatsApp Status using React. The component should handle temporary data, user interaction, and efficient resource management without a backend.
Functional Requirements
- Story Upload: Allow users to select an image file. This image should immediately appear in a horizontal scrollable list.
- Auto-Deletion: Each uploaded story must automatically disappear from the list exactly 15 seconds after upload.
- Story Viewing: Clicking a story preview should open the image in a centered modal (lightbox).
- Auto-Close Modal: The modal should automatically close after 3 seconds of viewing.
- Lifecycle Management: Ensure all timers and memory references are cleaned up if the component unmounts.
Technical Constraints
- Use functional components and React Hooks.
- Do not use external libraries for state or UI (e.g., Redux, Bootstrap).
- Ensure strict mode compatibility (no side effects in render).
Implementation Notes & key Concepts
1. State Management vs. References
Concept: The difference between useState and useRef is arguably the most common theoretical question in React interviews.
-
useState: Triggers a re-render when the value changes. Use this when the change needs to be reflected in the UI immediately.- Usage: Storing the list of
stories(adding/removing updates the list) andactiveStory(opening logic).
- Usage: Storing the list of
-
useRef: Holds a mutable value that persists across renders without triggering a re-render.- Usage: Storing
setTimeoutIDs. When a timer starts, we need to save its ID to clear it later. However, knowing the ID changed typically does not require updating the DOM, souseStatewould cause unnecessary performance overhead.
- Usage: Storing
2. The "Closure Trap" in Asynchronous Operations
Concept: Why do we use setStories(prev => ...) instead of setStories(stories ...)?
In JavaScript, functions "close over" variables in their scope. When setTimeout is declared, it captures the value of the state at that exact moment.
Incorrect Approach
// Assume 'stories' is an empty array [] at t=0
setTimeout(() => {
// 15 seconds later, this function runs.
// It still thinks 'stories' is [], even if the user added 5 items since then.
const newStories = stories.filter((s) => s.id !== id);
setStories(newStories); // This will accidentally wipe out recent additions.
}, 15000);Correct Approach (Functional Update)
setTimeout(() => {
// React guarantees 'prevStories' is the current, most up-to-date state.
setStories((prevStories) => prevStories.filter((s) => s.id !== id));
}, 15000);3. Memory Management: Object URLs
Concept: Handling file uploads on the client side without uploading to a server.
URL.createObjectURL(file): Creates a DOMString containing a URL representing the object given in the parameter. It is tied to the document's lifecycle.- Memory Leak Risk: The browser keeps the reference to the file in memory until the document is unloaded. If a user uploads 50 high-res photos, the app will crash if memory is not freed.
URL.revokeObjectURL(url): Manually releases an existing object URL.- Implementation: This must be called whenever a story is deleted (via the 15s timer) or when the component unmounts (via
useEffectcleanup).
- Implementation: This must be called whenever a story is deleted (via the 15s timer) or when the component unmounts (via
4. Component Cleanup
Concept: Preventing side effects on unmounted components.
If a user uploads a story (starts a 15s timer) and immediately navigates away from the page, the component unmounts. 15 seconds later, the timer fires and attempts to call setStories.
- Error: "Can't perform a React state update on an unmounted component."
- Solution: Track all active timer IDs in a
useRefobject. Return a cleanup function inuseEffectto clear them.
useEffect(() => {
// Cleanup function runs on unmount
return () => {
// 1. Clear all active timers
Object.values(storyTimersRef.current).forEach(clearTimeout);
// 2. Clear modal timer
if (modalTimerRef.current) clearTimeout(modalTimerRef.current);
// 3. Revoke the object URLs of stories that are still alive
storiesRef.current.forEach((story) => URL.revokeObjectURL(story.url));
};
}, []);storiesRef mirrors the stories state in its own effect. If this cleanup listed stories as a dependency instead, it would run on every upload, clear every expiry timer and revoke URLs that are still on screen.
Code Walkthrough
InstagramStory.jsx
The smart container component.
uploadStory: Handles file input. Generates a UUID and Blob URL. Starts the 15s expiry timer.storyTimersRef: A dictionary object{ [id]: timerId }. We use an object instead of an array allows O(1) access to clear specific timers by Story ID.handleStoryClick: Sets theactiveStorystate and starts the 3s modal auto-close timer.
Modal.jsx
The presentational component.
- Props: Receives
imageSrcandonClose. - Escape Key Listener: Adds a global event listener for accessibility. Note the cleanup in
useEffectto remove the listener.