react-ui-patterns by sickn33/antigravity-awesome-skills
npx skills add https://github.com/sickn33/antigravity-awesome-skills --skill react-ui-patterns仅在没有任何数据可显示时显示加载指示器。
// 正确 - 仅在无数据时显示加载
const { data, loading, error } = useGetItemsQuery();
if (error) return <ErrorState error={error} onRetry={refetch} />;
if (loading && !data) return <LoadingState />;
if (!data?.items.length) return <EmptyState />;
return <ItemList items={data.items} />;
// 错误 - 即使有缓存数据也显示加载器(重新获取时会闪烁!)
if (loading) return <LoadingState />;
是否有错误?
→ 是:显示错误状态并提供重试选项
→ 否:继续
是否正在加载且没有数据?
→ 是:显示加载指示器(旋转器/骨架屏)
→ 否:继续
是否有数据?
→ 是,且有项目:显示数据
→ 是,但为空:显示空状态
→ 否:显示加载状态(后备方案)
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
| 使用骨架屏的场景 | 使用旋转器的场景 |
|---|---|
| 已知内容形状 | 未知内容形状 |
| 列表/卡片布局 | 模态框操作 |
| 初始页面加载 | 按钮提交 |
| 内容占位符 | 内联操作 |
1. 内联错误(字段级别)→ 表单验证错误
2. 通知提示 → 可恢复错误,用户可以重试
3. 错误横幅 → 页面级错误,数据仍部分可用
4. 完整错误屏幕 → 不可恢复,需要用户操作
关键:绝不要静默吞掉错误。
// 正确 - 错误始终暴露给用户
const [createItem, { loading }] = useCreateItemMutation({
onCompleted: () => {
toast.success({ title: 'Item created' });
},
onError: (error) => {
console.error('createItem failed:', error);
toast.error({ title: 'Failed to create item' });
},
});
// 错误 - 错误被静默捕获,用户毫不知情
const [createItem] = useCreateItemMutation({
onError: (error) => {
console.error(error); // 用户什么都看不到!
},
});
interface ErrorStateProps {
error: Error;
onRetry?: () => void;
title?: string;
}
const ErrorState = ({ error, onRetry, title }: ErrorStateProps) => (
<div className="error-state">
<Icon name="exclamation-circle" />
<h3>{title ?? 'Something went wrong'}</h3>
<p>{error.message}</p>
{onRetry && (
<Button onClick={onRetry}>Try Again</Button>
)}
</div>
);
<Button
onClick={handleSubmit}
isLoading={isSubmitting}
disabled={!isValid || isSubmitting}
>
Submit
</Button>
关键:在异步操作期间始终禁用触发器。
// 正确 - 加载时按钮被禁用
<Button
disabled={isSubmitting}
isLoading={isSubmitting}
onClick={handleSubmit}
>
Submit
</Button>
// 错误 - 用户可以多次点击
<Button onClick={handleSubmit}>
{isSubmitting ? 'Submitting...' : 'Submit'}
</Button>
每个列表/集合都必须有空状态:
// 错误 - 没有空状态
return <FlatList data={items} />;
// 正确 - 显式空状态
return (
<FlatList
data={items}
ListEmptyComponent={<EmptyState />}
/>
);
// 搜索无结果
<EmptyState
icon="search"
title="No results found"
description="Try different search terms"
/>
// 列表尚无项目
<EmptyState
icon="plus-circle"
title="No items yet"
description="Create your first item"
action={{ label: 'Create Item', onClick: handleCreate }}
/>
const MyForm = () => {
const [submit, { loading }] = useSubmitMutation({
onCompleted: handleSuccess,
onError: handleError,
});
const handleSubmit = async () => {
if (!isValid) {
toast.error({ title: 'Please fix errors' });
return;
}
await submit({ variables: { input: values } });
};
return (
<form>
<Input
value={values.name}
onChange={handleChange('name')}
error={touched.name ? errors.name : undefined}
/>
<Button
type="submit"
onClick={handleSubmit}
disabled={!isValid || loading}
isLoading={loading}
>
Submit
</Button>
</form>
);
};
// 错误 - 数据存在时显示旋转器(会导致闪烁)
if (loading) return <Spinner />;
// 正确 - 仅在没有数据时显示加载
if (loading && !data) return <Spinner />;
// 错误 - 错误被吞掉
try {
await mutation();
} catch (e) {
console.log(e); // 用户毫不知情!
}
// 正确 - 错误被暴露
onError: (error) => {
console.error('operation failed:', error);
toast.error({ title: 'Operation failed' });
}
// 错误 - 提交期间按钮未禁用
<Button onClick={submit}>Submit</Button>
// 正确 - 禁用并显示加载状态
<Button onClick={submit} disabled={loading} isLoading={loading}>
Submit
</Button>
完成任何 UI 组件前检查:
UI 状态:
数据与变更:
此技能适用于执行概述中描述的工作流或操作。
每周安装次数
638
仓库
GitHub 星标数
27.1K
首次出现时间
2026年1月19日
安全审计
安装于
opencode521
gemini-cli506
codex466
cursor436
github-copilot431
claude-code428
Show loading indicator ONLY when there's no data to display.
// CORRECT - Only show loading when no data exists
const { data, loading, error } = useGetItemsQuery();
if (error) return <ErrorState error={error} onRetry={refetch} />;
if (loading && !data) return <LoadingState />;
if (!data?.items.length) return <EmptyState />;
return <ItemList items={data.items} />;
// WRONG - Shows spinner even when we have cached data
if (loading) return <LoadingState />; // Flashes on refetch!
Is there an error?
→ Yes: Show error state with retry option
→ No: Continue
Is it loading AND we have no data?
→ Yes: Show loading indicator (spinner/skeleton)
→ No: Continue
Do we have data?
→ Yes, with items: Show the data
→ Yes, but empty: Show empty state
→ No: Show loading (fallback)
| Use Skeleton When | Use Spinner When |
|---|---|
| Known content shape | Unknown content shape |
| List/card layouts | Modal actions |
| Initial page load | Button submissions |
| Content placeholders | Inline operations |
1. Inline error (field-level) → Form validation errors
2. Toast notification → Recoverable errors, user can retry
3. Error banner → Page-level errors, data still partially usable
4. Full error screen → Unrecoverable, needs user action
CRITICAL: Never swallow errors silently.
// CORRECT - Error always surfaced to user
const [createItem, { loading }] = useCreateItemMutation({
onCompleted: () => {
toast.success({ title: 'Item created' });
},
onError: (error) => {
console.error('createItem failed:', error);
toast.error({ title: 'Failed to create item' });
},
});
// WRONG - Error silently caught, user has no idea
const [createItem] = useCreateItemMutation({
onError: (error) => {
console.error(error); // User sees nothing!
},
});
interface ErrorStateProps {
error: Error;
onRetry?: () => void;
title?: string;
}
const ErrorState = ({ error, onRetry, title }: ErrorStateProps) => (
<div className="error-state">
<Icon name="exclamation-circle" />
<h3>{title ?? 'Something went wrong'}</h3>
<p>{error.message}</p>
{onRetry && (
<Button onClick={onRetry}>Try Again</Button>
)}
</div>
);
<Button
onClick={handleSubmit}
isLoading={isSubmitting}
disabled={!isValid || isSubmitting}
>
Submit
</Button>
CRITICAL: Always disable triggers during async operations.
// CORRECT - Button disabled while loading
<Button
disabled={isSubmitting}
isLoading={isSubmitting}
onClick={handleSubmit}
>
Submit
</Button>
// WRONG - User can tap multiple times
<Button onClick={handleSubmit}>
{isSubmitting ? 'Submitting...' : 'Submit'}
</Button>
Every list/collection MUST have an empty state:
// WRONG - No empty state
return <FlatList data={items} />;
// CORRECT - Explicit empty state
return (
<FlatList
data={items}
ListEmptyComponent={<EmptyState />}
/>
);
// Search with no results
<EmptyState
icon="search"
title="No results found"
description="Try different search terms"
/>
// List with no items yet
<EmptyState
icon="plus-circle"
title="No items yet"
description="Create your first item"
action={{ label: 'Create Item', onClick: handleCreate }}
/>
const MyForm = () => {
const [submit, { loading }] = useSubmitMutation({
onCompleted: handleSuccess,
onError: handleError,
});
const handleSubmit = async () => {
if (!isValid) {
toast.error({ title: 'Please fix errors' });
return;
}
await submit({ variables: { input: values } });
};
return (
<form>
<Input
value={values.name}
onChange={handleChange('name')}
error={touched.name ? errors.name : undefined}
/>
<Button
type="submit"
onClick={handleSubmit}
disabled={!isValid || loading}
isLoading={loading}
>
Submit
</Button>
</form>
);
};
// WRONG - Spinner when data exists (causes flash)
if (loading) return <Spinner />;
// CORRECT - Only show loading without data
if (loading && !data) return <Spinner />;
// WRONG - Error swallowed
try {
await mutation();
} catch (e) {
console.log(e); // User has no idea!
}
// CORRECT - Error surfaced
onError: (error) => {
console.error('operation failed:', error);
toast.error({ title: 'Operation failed' });
}
// WRONG - Button not disabled during submission
<Button onClick={submit}>Submit</Button>
// CORRECT - Disabled and shows loading
<Button onClick={submit} disabled={loading} isLoading={loading}>
Submit
</Button>
Before completing any UI component:
UI States:
Data & Mutations:
This skill is applicable to execute the workflow or actions described in the overview.
Weekly Installs
638
Repository
GitHub Stars
27.1K
First Seen
Jan 19, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode521
gemini-cli506
codex466
cursor436
github-copilot431
claude-code428
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
106,200 周安装