Learn to master loading state in React to improve UX. Discover how to conditionally render spinners while your API fetches data for your movie app.
Previously in this course, we covered fetching data from an API to populate our application. While that allows us to retrieve data, it introduces a "gap" in the user experience: the time between the request being sent and the data arriving. If we simply leave the UI blank during this interval, users often assume the app is broken.
In this lesson, we will implement a robust loading state to bridge that gap. By managing a dedicated state variable and applying conditional rendering, we can show the user that something is happening, significantly improving the perceived performance of our movie browser.
In a real-world application, network latency is unpredictable. Even a fast API can take 500ms to respond, which feels like an eternity on the web. By explicitly showing a "Loading..." message or a CSS spinner, we provide immediate feedback. This simple shift in UX transforms a perceived "hang" into a managed data-fetching process.
To track whether our data is currently being fetched, we introduce a new piece of state. We’ll use a boolean value: true when the request is in progress, and false once the data is returned or an error occurs.
Here is how we integrate this into our existing MovieList component:
JSXimport { useState, useEffect } from CE9178">'react'; function MovieList() { const [movies, setMovies] = useState([]); const [isLoading, setIsLoading] = useState(true); // 1. Initialize as true useEffect(() => { async function fetchMovies() { setIsLoading(true); // 2. Set to true before the fetch try { const response = await fetch(CE9178">'https://api.example.com/movies'); const data = await response.json(); setMovies(data); } catch (error) { console.error("Failed to fetch", error); } finally { setIsLoading(false); // 3. Always set to false when done } } fetchMovies(); }, []); // 4. Conditional Rendering based on state if (isLoading) { return <div className="spinner">Loading movies...</div>; } return ( <ul> {movies.map(movie => <li key={movie.id}>{movie.title}</li>)} </ul> ); }
Notice the use of the finally block in the example above. This is a best practice. Whether the API call succeeds or fails, the loading state must be set back to false. Without it, if the API throws an error, your user would be stuck with a permanent "Loading..." indicator—a classic "dead-end" UI bug.
Building on our movie-browser project, locate your movie fetching component. Perform the following steps:
const [isLoading, setIsLoading] = useState(true); line to your component.fetch call logic to set isLoading(true) at the start and setIsLoading(false) inside a finally block.isLoading is true.finally block: As mentioned, if you only set isLoading(false) inside the .then() or after the await, an error will leave your application in a permanent loading state.Managing a loading state is a fundamental step in building professional React applications. By tracking the lifecycle of an asynchronous request, you can provide clear visual feedback, which is essential for a high-quality UX. We’ve learned to initialize our state, use finally to ensure clean transitions, and use conditional rendering to swap our view based on the current operation status.
Up next: Now that we have a loading state, we need to handle scenarios where the API request fails entirely; we'll cover managing errors in our next lesson.
Master the art of UI state management by adding a filter toggle to your movie app. Learn to control list rendering using boolean state and checkbox inputs.
Handling Loading States
Polishing the UI
Finalizing the Movie Browser
Review of Component Lifecycle
Review of State Management
Building a Modal Component
Introduction to PropTypes
Performance Optimization Basics
Handling Browser History
Working with LocalStorage
Building a Favorites List
Handling Media in React
Introduction to Testing
Debugging React Apps
Deployment Basics
Using External Libraries
Advanced