同步 · dev.to / @markyu
A practical React loading screen guide using hooks, request states, error states, CSS animation, and why spinners alone are not enough.
- 发布日期
- May 3 '24
- 阅读时长
- 2 min read
- 点赞
- 7
A loading screen is not a spinner problem.
It is a state problem.
The UI needs to know whether the request is idle, loading, successful, empty, or failed. If you only track loading: true/false, your component will eventually lie to the user.
The State Shape I Prefer
const [state, setState] = useState({
status: "idle",
data: null,
error: null,
});The useful statuses:
idle -> loading -> success
\-> empty
\-> errorThis is already better than done.
A Practical Fetch Component
import { useEffect, useState } from "react";
export default function Posts() {
const [state, setState] = useState({
status: "idle",
data: [],
error: null,
});
useEffect(() => {
let ignore = false;
async function loadPosts() {
setState({ status: "loading", data: [], error: null });
try {
const res = await fetch("https://jsonplaceholder.typicode.com/posts");
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
if (ignore) return;
setState({
status: data.length ? "success" : "empty",
data,
error: null,
});
} catch (error) {
if (!ignore) {
setState({ status: "error", data: [], error });
}
}
}
loadPosts();
return () => {
ignore = true;
};
}, []);
if (state.status === "loading") return <LoadingScreen />;
if (state.status === "error") return <ErrorMessage error={state.error} />;
if (state.status === "empty") return <p>No posts yet.</p>;
return (
<ul>
{state.data.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}The ignore flag avoids setting state after the component unmounts. It is a small detail, but it prevents a class of annoying bugs.
CSS Spinner Without a Library
function LoadingScreen() {
return (
<div className="loader" role="status" aria-live="polite">
<span className="spinner" />
<p>Loading posts...</p>
</div>
);
}.loader {
min-height: 180px;
display: grid;
place-items: center;
gap: 12px;
}
.spinner {
width: 36px;
height: 36px;
border: 4px solid #e5e7eb;
border-top-color: #2563eb;
border-radius: 50%;
animation: spin 800ms linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: reduce) {
.spinner {
animation: none;
}
}I prefer this over adding a spinner dependency for a simple app.
When Lottie Makes Sense
Lottie is useful when loading is part of the product experience: onboarding, file upload, payment confirmation, or a long-running AI task.
I would not use it for every API fetch.
If the request usually takes 200ms, a dramatic animation feels slower, not better.
My Rule for Loading UI
| Load time | UI |
|---|---|
| < 300ms | avoid spinner if possible |
| 300ms - 2s | small inline loader |
| 2s - 10s | skeleton or progress context |
| > 10s | progress, cancellation, or background job |
For AI-agent and long-running 2026 workflows, the last row matters a lot. Users need to know whether the system is still thinking, stuck, or waiting on a tool.
Final Thought
A good loading screen tells the truth.
It should not just spin. It should represent the real state of the request and give the user enough confidence to wait or recover.
How do you handle long-running loading states in your React apps?
相关阅读
React 19 Micro-Interactions Without Layout Jank
A practical React 19 micro-interactions guide focused on motion boundaries, CSS transitions, optimistic UI, reduced motion, and performance.
react
CSS Heart Animation: Small Demo, Real Animation Lessons
A cleaner CSS heart animation tutorial focused on transform, pseudo-elements, keyframes, and the small mistakes that break simple UI animations.
css
Frontend Linear Data Structures Deep Dive: Arrays, Stacks, Queues, and Linked Lists
The Big Picture Before diving into stacks, queues, and linked lists, it helps to know...
computerscience
原文发布
本文首发于 dev.to,评论与点赞保留在原站。
在 dev.to 继续阅读