Questions
This component manages loading state imperatively. Refactor it to use React Suspense.
A developer has a UserProfile component that fetches user data from an API. Currently, they manage the loading state manually using useState and useEffect, displaying a “Loading…” message while waiting for the data.
They want to improve the user experience and simplify the code by using React Suspense to declaratively handle the loading state.
You’ve been given the UserProfile component and a mock API.
- Refactor the application to use
React.Suspenseto declaratively handle the loading state for theUserProfilecomponent. - The
UserProfilecomponent should “suspend” its rendering until the data is available. - The
Appcomponent should display afallbackUI whileUserProfileis waiting for data.
import React, { useState, useEffect } from 'react';
// Mock API call that simulates fetching user data
const fetchUserData = () => {
console.log('Fetching user data...');
return new Promise(resolve => {
setTimeout(() => {
resolve({ id: 1, name: 'Alice', email: 'alice@example.com' });
}, 2000); // Simulate network delay
});
};
// UserProfile component with imperative loading state management
function UserProfile() {
const [userData, setUserData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
setLoading(true);
fetchUserData().then(data => {
setUserData(data);
setLoading(false);
});
}, []);
if (loading) {
return <div>Loading user profile...</div>;
}
return (
<div style={{ border: '1px solid #ccc', padding: '20px', margin: '10px' }}>
<h2>User Profile</h2>
<p>ID: {userData.id}</p>
<p>Name: {userData.name}</p>
<p>Email: {userData.email}</p>
</div>
);
}
export default function App() {
return (
<div style={{ padding: '20px' }}>
<h1>Application</h1>
<UserProfile />
</div>
);
} How Different Experience Levels Approach This
import React, { useState, useEffect } from 'react'; // Mock API call that simulates fetching user data const fetchUserData = () => { console.log('Fetching user data...'); return new Promise(resolve => { setTimeout(() => { resolve({ id: 1, name: 'Alice', email: 'alice@example.com' }); }, 2000); // Simulate network delay }); }; // UserProfile component with imperative loading state management function UserProfile() { const [userData, setUserData] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { setLoading(true); fetchUserData().then(data => { setUserData(data); setLoading(false); }); }, []); if (loading) { return <div>Loading user profile...</div>; } return ( <div style={{ border: '1px solid #ccc', padding: '20px', margin: '10px' }}> <h2>User Profile</h2> <p>ID: {userData.id}</p> <p>Name: {userData.name}</p> <p>Email: {userData.email}</p> </div> ); } export default function App() { return ( <div style={{ padding: '20px' }}> <h1>Application</h1> <UserProfile /> </div> ); }
import React, { useState, useEffect, Suspense } from 'react'; // 1. Create a Suspense-compatible resource utility // This is a simplified example. In a real app, you'd use a library like // React Query, SWR, or Relay, which provide Suspense integration. function createResource(promise) { let status = 'pending'; let result; let suspender = promise.then( r => { status = 'success'; result = r; }, e => { status = 'error'; result = e; } ); return { read() { if (status === 'pending') { throw suspender; // Suspense catches this promise } else if (status === 'error') { throw result; } else if (status === 'success') { return result; } } }; } // Mock API call that simulates fetching user data const fetchUserData = () => { console.log('Fetching user data...'); return new Promise(resolve => { setTimeout(() => { resolve({ id: 1, name: 'Alice', email: 'alice@example.com' }); }, 2000); // Simulate network delay }); }; // Create the resource once outside the component const userResource = createResource(fetchUserData()); // UserProfile component now uses the resource directly function UserProfile() { // 2. Read data from the resource. This will suspend if data is not ready. const userData = userResource.read(); console.log('UserProfile rendered with data:', userData); return ( <div style={{ border: '1px solid #ccc', padding: '20px', margin: '10px' }}> <h2>User Profile</h2> <p>ID: {userData.id}</p> <p>Name: {userData.name}</p> <p>Email: {userData.email}</p> </div> ); } export default function App() { return ( <div style={{ padding: '20px' }}> <h1>Application</h1> {/* 3. Wrap UserProfile with Suspense and provide a fallback */} <Suspense fallback={<div>Loading data with Suspense...</div>}> <UserProfile /> </Suspense> </div> ); }
- Context over facts: Explains when and why, not just what
- Real examples: Provides specific use cases from production experience
- Trade-offs: Acknowledges pros, cons, and decision factors
Practice Question
What is the primary purpose of React.Suspense?