Branch on structured loader errors
Use a loader's error.category and error.code when different failures need different UI.
DataSource and other loader-backed components normalize failed requests into an AppError. That gives you stable fields for branching instead of parsing message text. Use category for user-facing decisions, code for precise diagnostics, and data.statusCode when the HTTP status itself matters.
<App var.mode="ok">
<VStack padding="$space-4" gap="$space-3">
<HStack gap="$space-2">
<Button label="No error" onClick="mode = 'ok'" />
<Button label="Auth" onClick="mode = 'authorization'" />
<Button label="Missing" onClick="mode = 'not-found'" />
<Button label="Conflict" onClick="mode = 'conflict'" />
<Button label="Server" onClick="mode = 'server'" />
</HStack>
<DataSource id="profile" url="/api/profile/{mode}" />
<Card when="{mode === 'ok' && profile.value}" padding="$space-3">
<VStack gap="$space-2">
<Text variant="strong">No error</Text>
<Text>Loaded profile: {profile.value.name}</Text>
</VStack>
</Card>
<Card when="{mode !== 'ok' && profile.error}" padding="$space-3">
<VStack gap="$space-2">
<Text variant="strong">Category: {profile.error.category}</Text>
<Text>Code: {profile.error.code}</Text>
<Text>Status: {profile.error.data.statusCode}</Text>
<Text when="{profile.error.category === 'authorization'}">
Please sign in before opening this profile.
</Text>
<Text when="{profile.error.category === 'not-found'}">
This profile has not been provisioned yet.
</Text>
<Text when="{profile.error.category === 'conflict'}">
Someone else changed the profile. Refresh before saving.
</Text>
<Text when="{profile.error.category === 'server'}">
The server failed. Try again in a moment.
</Text>
</VStack>
</Card>
</VStack>
</App><App var.mode="ok">
<VStack padding="$space-4" gap="$space-3">
<HStack gap="$space-2">
<Button label="No error" onClick="mode = 'ok'" />
<Button label="Auth" onClick="mode = 'authorization'" />
<Button label="Missing" onClick="mode = 'not-found'" />
<Button label="Conflict" onClick="mode = 'conflict'" />
<Button label="Server" onClick="mode = 'server'" />
</HStack>
<DataSource id="profile" url="/api/profile/{mode}" />
<Card when="{mode === 'ok' && profile.value}" padding="$space-3">
<VStack gap="$space-2">
<Text variant="strong">No error</Text>
<Text>Loaded profile: {profile.value.name}</Text>
</VStack>
</Card>
<Card when="{mode !== 'ok' && profile.error}" padding="$space-3">
<VStack gap="$space-2">
<Text variant="strong">Category: {profile.error.category}</Text>
<Text>Code: {profile.error.code}</Text>
<Text>Status: {profile.error.data.statusCode}</Text>
<Text when="{profile.error.category === 'authorization'}">
Please sign in before opening this profile.
</Text>
<Text when="{profile.error.category === 'not-found'}">
This profile has not been provisioned yet.
</Text>
<Text when="{profile.error.category === 'conflict'}">
Someone else changed the profile. Refresh before saving.
</Text>
<Text when="{profile.error.category === 'server'}">
The server failed. Try again in a moment.
</Text>
</VStack>
</Card>
</VStack>
</App>Key points
Branch on category, not message text: Categories are stable semantic labels such as authorization, not-found, conflict, rate-limit, and server.
Use code for diagnostics: HTTP failures become codes like http-403 and http-500. Codes are useful in logs, support tickets, and QA assertions.
Read HTTP details from data: profile.error.data.statusCode contains the original status code. Other response details may also be present in data.
Retry only retryable categories: Server and network failures are retryable by default; validation, authorization, not-found, and conflict failures are not.
See also
- Retry retryable loader errors — retry only categories that can recover
- Show section fallback from a loader error — replace a failed section with recovery UI
- Structured Exception Model — full
AppErrorshape and category table