Why does my React component crash on the first render when using data fetched inside useEffect()?
When you fetch data inside useEffect(), the network request is asynchronous and only runs after the initial render has already completed and painted to the screen. Because React cannot pause rendering to wait for your API response, your component's state is still its initial value (like null or undefined) during that first render. If you try to read properties from your data (like user.name) before the data actually arrives, your app will crash.
Step-by-Step: The Async Lifecycle
function UserProfile({ userId }) {
const [user, setUser] = useState(null); // Starts as null
useEffect(() => {
fetchUser(userId).then(setUser); // Async API call
}, [userId]);
// โ ๏ธ Safety check is mandatory!
return (
<div>
{user ? <h1>{user.name}</h1> : <div>Loading...</div>}
</div>
);
}
Step 1: The Initial Render (The "Loading" State)
- React mounts the component.
- It initializes the state:
useris set tonull. - React must return JSX immediately. It cannot halt the browser to wait on a server.
- React runs your
returnstatement. Sinceuseris currentlynull, the ternary operator evaluates the fallback and renders:<div>Loading...</div>. - The browser paints "Loading..." on the screen. The first render is now complete.
Step 2: The useEffect Fires (After Paint)
Now that the first render is safely on the screen, React triggers the useEffect(). It kicks off your asynchronous network call: fetchUser('123'). While the browser is waiting for the server to reply, nothing changes on the screen. It still shows "Loading..."
Step 3: State Updates
Sometime later (e.g., 300ms), the server responds with { name: "Alex" }. The promise resolves and calls setUser({ name: "Alex" }). Setting state signals React: "Hey! The data is here. We need to re-render!"
Step 4: The Second Render (The "Data" State)
React re-runs the UserProfile function. This time, the user is not null; it is { name: "Alex" }. The return statement runs again. The ternary operator evaluates to truthy and returns: <h1>Alex</h1>. The browser updates, replacing "Loading..." with "Alex."
โ ๏ธ Why Conditional Guards Are Mandatory
Because of this exact asynchronous timeline, if you do not write a guard condition, your application will crash during Step 1.
// โ THIS WILL CRASH IMMEDIATELY
return (
<div>
<h1>{user.name}</h1>
</div>
);
During the initial render, user is still null. JavaScript will try to read null.name and throw the classic error:
๐ก TypeError: Cannot read properties of null (reading 'name')
Always protect your renders from empty async states using either ternary operators, logical AND (&&), or optional chaining (?.):
// Option A: Ternary Operator (Great for loading states)
{user ? <h1>{user.name}</h1> : <div>Loading...</div>}
// Option B: Optional Chaining (Safely returns undefined instead of crashing)
{user?.name}
Put your comments; I'll be happy to see feedback if it helps anyone. Follow me for more.
Comments
No comments yet. Start the discussion.