React Suspenseful Data Fetching Challenge
This challenge focuses on implementing data fetching with React Suspense, a powerful feature for handling asynchronous operations and improving user experience. You will build a component that fetches data from a simulated API and displays it, leveraging Suspense to gracefully handle loading states and errors. This is crucial for creating smooth, modern user interfaces where data loading doesn't block the entire application.
Problem Description
Your task is to create a React component that fetches data from a given API endpoint using a Suspense-enabled data fetching library or pattern. The component should:
- Fetch Data: Retrieve data from a provided API URL.
- Handle Loading States: When data is being fetched, display a placeholder or loading indicator using React Suspense's
fallbackprop. - Display Data: Once the data is successfully fetched, render it within the component.
- Handle Errors: If an error occurs during data fetching, display an appropriate error message. You should utilize React Error Boundaries to catch these errors.
- Simulated API: You will be provided with a mock API function that simulates network latency and potential errors.
Key Requirements:
- Use TypeScript for all code.
- Implement the solution using React Suspense.
- The component should accept a
resourceobject that encapsulates the data fetching logic and its status (loading, success, error). - A dedicated loading fallback should be displayed during the initial data fetch.
- Error handling should be robust, catching and displaying errors gracefully.
Expected Behavior:
- Initial Load: When the component first renders, the loading fallback should be visible.
- Data Loaded: After the data is fetched successfully, the component should render the fetched data.
- Fetch Error: If the API call fails, an error message should be displayed, and the loading fallback should not be shown.
Edge Cases:
- Consider what happens if the
resourceobject is not yet ready or is in an indeterminate state. - Handle scenarios where the API might return empty data.
Examples
Example 1: Successful Data Fetch
Let's assume you have a resource object created by a data fetching utility (like React Query, Apollo Client, or a custom Suspense-compatible solution) that handles fetching a list of users.
// Assume this resource is already created and wrapped in suspense-friendly logic
interface User {
id: number;
name: string;
}
interface Resource<T> {
read(): T; // Throws an error if loading/error, returns data if successful
}
// Mock resource for demonstration
const mockUserResource: Resource<{ users: User[] }> = {
read: () => {
// In a real scenario, this would trigger a fetch and potentially throw a Promise or Error
// For this example, we'll simulate immediate success
return {
users: [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
],
};
},
};
// Your component usage within a Suspense boundary
function UserListComponent({ resource }: { resource: Resource<{ users: User[] }> }) {
const data = resource.read();
return (
<div>
<h2>Users</h2>
<ul>
{data.users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}
// Parent component setup
function App() {
return (
<Suspense fallback={<div>Loading users...</div>}>
<UserListComponent resource={mockUserResource} />
</Suspense>
);
}
// Expected rendered output after data is "fetched":
// <div>
// <h2>Users</h2>
// <ul>
// <li>Alice</li>
// <li>Bob</li>
// </ul>
// </div>
Example 2: Loading State
If the resource.read() method internally throws a Promise indicating that the data is still loading, Suspense will automatically render the fallback provided to the Suspense component.
// Mock resource simulating loading
const mockLoadingResource: Resource<{ users: User[] }> = {
read: () => {
// Simulate a network request in progress
throw new Promise(() => {}); // A promise that never resolves (for simulation)
},
};
// Parent component setup
function App() {
return (
<Suspense fallback={<div>Loading users...</div>}>
<UserListComponent resource={mockLoadingResource} />
</Suspense>
);
}
// Expected rendered output:
// <div>Loading users...</div>
Example 3: Error State
If the resource.read() method throws an Error (or a Promise that rejects), the nearest Error Boundary will catch it.
// Mock resource simulating an error
const mockErrorResource: Resource<{ users: User[] }> = {
read: () => {
throw new Error("Failed to fetch users!");
},
};
// Component to render errors
function ErrorFallback({ error }: { error: Error }) {
return <div>Error: {error.message}</div>;
}
// Parent component setup
function App() {
return (
<ErrorBoundary fallback={<ErrorFallback />}>
<Suspense fallback={<div>Loading users...</div>}>
<UserListComponent resource={mockErrorResource} />
</Suspense>
</ErrorBoundary>
);
}
// Expected rendered output:
// <div>Error: Failed to fetch users!</div>
Constraints
- API Simulation: You will be provided with a
fetchDatafunction that returns aPromise. This function will simulate network latency (usingsetTimeout) and can be configured to sometimes reject with an error.type FetchDataConfig = { delay: number; // milliseconds shouldError: boolean; }; async function fetchData<T>(url: string, config?: FetchDataConfig): Promise<T>; - Data Structure: The data fetched will be a simple JSON object, e.g.,
{ data: any }. - Component Structure: You are to build a
SuspensefulDataFetchercomponent that accepts a URL and configuration for thefetchDatafunction. - No Direct State Management: Avoid using
useStateoruseEffectdirectly for managing the fetched data or loading/error states within yourSuspensefulDataFetchercomponent. Leverage Suspense and Error Boundaries. - TypeScript: All code must be written in TypeScript.
Notes
- You'll need to create a way to "suspend" the component's rendering while data is being fetched. A common pattern is to wrap your data fetching logic in a custom hook or utility that throws a
Promisewhen loading and throws anErrorwhen an error occurs. This is what theresource.read()method in the examples simulates. - Consider how to create a reusable
resourceobject that can be passed down. - Remember to set up an
ErrorBoundaryto catch any errors thrown by your data fetching mechanism. - Think about how to make your
SuspensefulDataFetcherconfigurable to control the loading fallback and error display.