Skip to main content

isError

The isError function is a TypeScript type guard that checks whether a NetworkState represents a failed API response. It provides type-safe access to error information by narrowing the type from a general NetworkState<T, E> to a specific ErrorState<T, E>.

Overview

isError is essential for safely handling network request failures in your React components. It ensures that you can only access the error property when the request has actually failed, preventing runtime errors and providing excellent TypeScript intellisense for error handling.

Function Signature

function isError<T>(
state: NetworkState<T>
): state is ErrorState<T>

Type Parameters

ParameterDescription
TThe type of the successful response data

Parameters

ParameterTypeDescription
stateNetworkState<T>The network state to check

Returns

TypeDescription
state is ErrorState<T>Type predicate that narrows the state to ErrorState<T> when true

ErrorState Interface

When isError returns true, the state is narrowed to ErrorState<T> which includes:

PropertyTypeDescription
state'error'Always 'error' for error states
errorIntrigErrorThe structured error object containing type, status, and context information

Basic Usage

Simple Error Handling

import React from 'react';
import { useGetUsers, isError, isPending, isSuccess } from '@intrig/react';

function UsersList() {
const [usersResponse, fetchUsers] = useGetUsers({
fetchOnMount: true
});

if (isPending(usersResponse)) {
return <div>Loading users...</div>;
}

if (isError(usersResponse)) {
// TypeScript now knows usersResponse.error is available and properly typed
return (
<div className="error">
<h2>Failed to load users</h2>
<p>Error: {String(usersResponse.error)}</p>
<button onClick={() => fetchUsers()}>
Try Again
</button>
</div>
);
}

if (isSuccess(usersResponse)) {
return (
<div>
<h2>Users ({usersResponse.data.length})</h2>
<ul>
{usersResponse.data.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}

return <div>Click to load users</div>;
}

Detailed Error Display

import React from 'react';
import { useCreateUser, isError, isPending } from '@intrig/react';

function CreateUserForm() {
const [createResponse, createUser] = useCreateUser({
fetchOnMount: false
});

const handleSubmit = async (formData) => {
await createUser({ body: formData });
};

if (isPending(createResponse)) {
return <div>Creating user...</div>;
}

if (isError(createResponse)) {
const { error, statusCode } = createResponse;

return (
<div className="error-container">
<h3>Failed to create user</h3>

{statusCode === 400 && (
<p>Please check your input data</p>
)}

{statusCode === 401 && (
<p>You are not authorized to create users</p>
)}

{statusCode === 500 && (
<p>Server error occurred. Please try again later.</p>
)}

<details>
<summary>Error Details</summary>
<pre>{JSON.stringify(error, null, 2)}</pre>
</details>

<button onClick={() => handleSubmit()}>
Retry
</button>
</div>
);
}

return (
<form onSubmit={handleSubmit}>
{/* Form fields */}
</form>
);
}

Global Error Handler

import React from 'react';
import { useGetData, isError, isPending, isSuccess } from '@intrig/react';

function DataWithErrorBoundary() {
const [dataResponse] = useGetData({
fetchOnMount: true
});

const renderError = (errorState) => {
const { error, statusCode } = errorState;

// Handle different error types
if (statusCode === 404) {
return (
<div className="not-found">
<h2>Data Not Found</h2>
<p>The requested data could not be found.</p>
</div>
);
}

if (statusCode >= 500) {
return (
<div className="server-error">
<h2>Server Error</h2>
<p>Something went wrong on our end. Please try again later.</p>
</div>
);
}

if (statusCode === 403) {
return (
<div className="forbidden">
<h2>Access Denied</h2>
<p>You don't have permission to access this resource.</p>
</div>
);
}

// Generic error fallback
return (
<div className="generic-error">
<h2>Something went wrong</h2>
<p>{String(error)}</p>
</div>
);
};

if (isPending(dataResponse)) {
return <div>Loading...</div>;
}

if (isError(dataResponse)) {
return renderError(dataResponse);
}

if (isSuccess(dataResponse)) {
return <div>Data: {JSON.stringify(dataResponse.data)}</div>;
}

return <div>Ready to load data</div>;
}

Advanced Usage

Error Classification

import React from 'react';
import { useGetUserProfile, isError } from '@intrig/react';

function UserProfile({ userId }) {
const [profileResponse] = useGetUserProfile({
fetchOnMount: true,
params: { userId }
});

if (isError(profileResponse)) {
const { error, statusCode } = profileResponse;

// Classify error types
const isNetworkError = !statusCode;
const isClientError = statusCode && statusCode >= 400 && statusCode < 500;
const isServerError = statusCode && statusCode >= 500;
const isValidationError = statusCode === 422;

if (isNetworkError) {
return (
<div className="network-error">
<h3>Network Error</h3>
<p>Please check your internet connection</p>
</div>
);
}

if (isValidationError) {
return (
<div className="validation-error">
<h3>Invalid Data</h3>
<p>{String(error)}</p>
</div>
);
}

if (isClientError) {
return (
<div className="client-error">
<h3>Request Error ({statusCode})</h3>
<p>{String(error)}</p>
</div>
);
}

if (isServerError) {
return (
<div className="server-error">
<h3>Server Error ({statusCode})</h3>
<p>Please try again later</p>
</div>
);
}
}

// Handle other states...
return null;
}

Error Recovery Pattern

import React, { useState, useCallback } from 'react';
import { useGetData, isError, isPending } from '@intrig/react';

function DataWithRetry() {
const [retryCount, setRetryCount] = useState(0);
const [dataResponse, fetchData] = useGetData({
fetchOnMount: true
});

const handleRetry = useCallback(() => {
setRetryCount(prev => prev + 1);
fetchData();
}, [fetchData]);

if (isPending(dataResponse)) {
return (
<div>
Loading data...
{retryCount > 0 && <span> (Attempt {retryCount + 1})</span>}
</div>
);
}

if (isError(dataResponse)) {
const { error, statusCode } = dataResponse;
const maxRetries = 3;
const canRetry = retryCount < maxRetries;

return (
<div className="error-with-retry">
<h3>Failed to Load Data</h3>
<p>Error: {String(error)}</p>
{statusCode && <p>Status: {statusCode}</p>}

{canRetry ? (
<button onClick={handleRetry}>
Retry ({retryCount}/{maxRetries})
</button>
) : (
<p>Maximum retry attempts reached. Please refresh the page.</p>
)}
</div>
);
}

// Handle success state...
return null;
}

Custom Error Types

import React from 'react';
import { useGetUsers, isError } from '@intrig/react';

interface APIError {
message: string;
code: string;
details?: Record<string, any>;
}

function UsersWithTypedErrors() {
const [usersResponse] = useGetUsers({
fetchOnMount: true
});

if (isError(usersResponse)) {
const { error, statusCode } = usersResponse;

// Type-safe error handling when you know the error structure
const apiError = error as APIError;

return (
<div className="typed-error">
<h3>Error Loading Users</h3>
<p>Message: {apiError.message}</p>
<p>Code: {apiError.code}</p>
<p>Status: {statusCode}</p>

{apiError.details && (
<details>
<summary>Additional Details</summary>
<pre>{JSON.stringify(apiError.details, null, 2)}</pre>
</details>
)}
</div>
);
}

// Handle other states...
return null;
}

Common Patterns

Error Toast Notifications

import React, { useEffect } from 'react';
import { useGetData, isError } from '@intrig/react';
import { toast } from 'react-toastify';

function DataWithErrorToasts() {
const [dataResponse] = useGetData({
fetchOnMount: true
});

useEffect(() => {
if (isError(dataResponse)) {
const { error, statusCode } = dataResponse;
toast.error(`Failed to load data: ${String(error)} (${statusCode})`);
}
}, [dataResponse]);

// Component rendering logic...
return null;
}

Conditional Error Boundary

function ErrorBoundaryWrapper({ children, response }) {
if (isError(response)) {
return <GlobalErrorFallback error={response} />;
}

return children;
}

function MyComponent() {
const [dataResponse] = useGetData({ fetchOnMount: true });

return (
<ErrorBoundaryWrapper response={dataResponse}>
<DataDisplay response={dataResponse} />
</ErrorBoundaryWrapper>
);
}

Type Safety Benefits

The isError function provides several TypeScript benefits:

  1. Type Narrowing: After the check, TypeScript knows you have an ErrorState<T, E>
  2. Intellisense: Full autocomplete support for error properties
  3. Compile-time Safety: Prevents accessing error properties when they might not exist
  4. Error Prevention: Eliminates runtime errors from undefined error access

Best Practices

  1. Always handle errors gracefully: Use isError to provide meaningful error messages to users
  2. Classify errors appropriately: Different error types should have different handling strategies
  3. Provide retry mechanisms: Allow users to retry failed requests when appropriate
  4. Use status codes: Leverage HTTP status codes for more specific error handling
  5. Consider error recovery: Implement patterns that help users recover from errors
  6. Log errors appropriately: Send error information to monitoring services while being mindful of sensitive data

Troubleshooting

Common Issues

Issue: Error property is undefined even after isError check

// ❌ Bad - might happen if error state is not properly constructed
if (isError(response)) {
console.log(response.error); // Could be undefined
}

// ✅ Good - always handle the case where error might be undefined
if (isError(response)) {
const errorMessage = response.error ? String(response.error) : 'Unknown error';
console.log(errorMessage);
}

Issue: Status code not available

// ✅ Always check if statusCode exists before using it
if (isError(response)) {
const statusCode = response.statusCode ?? 0;
const errorType = statusCode >= 500 ? 'server' : 'client';
}

Issue: Error not properly typed

// ✅ Use type assertions carefully with proper error interfaces
interface APIError {
message: string;
code: string;
}

if (isError(response)) {
const apiError = response.error as APIError;
// Now you have typed access to error properties
}