try-error Documentation

React Examples

Real-world examples of using try-error in React applications

User Dashboard with Multiple Data Sources

A complete example showing how to handle multiple async operations with different error handling strategies.

User Dashboard Componenttsx
1import { useTryAsync, TryErrorBoundary } from '@try-error/react';
2import { tryAsync, isTryError } from 'try-error';
3
4interface User {
5  id: string;
6  name: string;
7  email: string;
8}
9
10interface UserStats {
11  postsCount: number;
12  followersCount: number;
13  followingCount: number;
14}
15
16interface Notification {
17  id: string;
18  message: string;
19  read: boolean;
20}
21
22function UserDashboard({ userId }: { userId: string }) {
23  // Critical data - must succeed
24  const { data: user, error: userError, loading: userLoading } = useTryAsync(
25    async () => {
26      const response = await fetch(`/api/users/${userId}`);
27      if (!response.ok) throw new Error('Failed to fetch user');
28      return response.json() as Promise<User>;
29    },
30    [userId]
31  );
32
33  // Optional data - can fail gracefully
34  const { data: stats, error: statsError } = useTryAsync(
35    async () => {
36      const response = await fetch(`/api/users/${userId}/stats`);
37      if (!response.ok) throw new Error('Failed to fetch stats');
38      return response.json() as Promise<UserStats>;
39    },
40    [userId]
41  );
42
43  const { data: notifications, error: notificationsError } = useTryAsync(
44    async () => {
45      const response = await fetch(`/api/users/${userId}/notifications`);
46      if (!response.ok) throw new Error('Failed to fetch notifications');
47      return response.json() as Promise<Notification[]>;
48    },
49    [userId]
50  );
51
52  if (userLoading) {
53    return (
54      <div className="animate-pulse space-y-4">
55        <div className="h-8 bg-gray-200 rounded w-1/3"></div>
56        <div className="h-4 bg-gray-200 rounded w-1/2"></div>
57        <div className="grid grid-cols-3 gap-4">
58          <div className="h-20 bg-gray-200 rounded"></div>
59          <div className="h-20 bg-gray-200 rounded"></div>
60          <div className="h-20 bg-gray-200 rounded"></div>
61        </div>
62      </div>
63    );
64  }
65
66  if (userError) {
67    return (
68      <div className="text-center py-8">
69        <div className="text-red-600 mb-4">
70          <h2 className="text-xl font-semibold">Failed to load user</h2>
71          <p>{userError.message}</p>
72        </div>
73        <button
74          onClick={() => window.location.reload()}
75          className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
76        >
77          Try Again
78        </button>
79      </div>
80    );
81  }
82
83  if (!user) return null;
84
85  return (
86    <div className="space-y-6">
87      {/* User Header */}
88      <div className="bg-white rounded-lg shadow p-6">
89        <h1 className="text-2xl font-bold text-gray-900">{user.name}</h1>
90        <p className="text-gray-600">{user.email}</p>
91      </div>
92
93      {/* Stats Section */}
94      <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
95        {stats ? (
96          <>
97            <StatCard title="Posts" value={stats.postsCount} />
98            <StatCard title="Followers" value={stats.followersCount} />
99            <StatCard title="Following" value={stats.followingCount} />
100          </>
101        ) : statsError ? (
102          <div className="col-span-3 bg-yellow-50 border border-yellow-200 rounded-lg p-4">
103            <p className="text-yellow-800">
104              ⚠️ Stats unavailable: {statsError.message}
105            </p>
106          </div>
107        ) : (
108          <div className="col-span-3 animate-pulse">
109            <div className="h-20 bg-gray-200 rounded"></div>
110          </div>
111        )}
112      </div>
113
114      {/* Notifications Section */}
115      <div className="bg-white rounded-lg shadow">
116        <div className="p-6 border-b">
117          <h2 className="text-lg font-semibold">Notifications</h2>
118        </div>
119        <div className="p-6">
120          {notifications ? (
121            notifications.length > 0 ? (
122              <div className="space-y-2">
123                {notifications.map((notification) => (
124                  <NotificationItem key={notification.id} notification={notification} />
125                ))}
126              </div>
127            ) : (
128              <p className="text-gray-500">No notifications</p>
129            )
130          ) : notificationsError ? (
131            <div className="text-yellow-600">
132              ⚠️ Notifications unavailable: {notificationsError.message}
133            </div>
134          ) : (
135            <div className="animate-pulse space-y-2">
136              <div className="h-4 bg-gray-200 rounded w-3/4"></div>
137              <div className="h-4 bg-gray-200 rounded w-1/2"></div>
138            </div>
139          )}
140        </div>
141      </div>
142    </div>
143  );
144}
145
146function StatCard({ title, value }: { title: string; value: number }) {
147  return (
148    <div className="bg-white rounded-lg shadow p-4 text-center">
149      <div className="text-2xl font-bold text-gray-900">{value.toLocaleString()}</div>
150      <div className="text-gray-600">{title}</div>
151    </div>
152  );
153}
154
155function NotificationItem({ notification }: { notification: Notification }) {
156  return (
157    <div className={`p-3 rounded ${notification.read ? 'bg-gray-50' : 'bg-blue-50'}`}>
158      <p className="text-sm">{notification.message}</p>
159    </div>
160  );
161}
162
163// Usage with Error Boundary
164function App() {
165  return (
166    <TryErrorBoundary
167      fallback={({ error, resetError }) => (
168        <div className="min-h-screen flex items-center justify-center">
169          <div className="text-center">
170            <h1 className="text-xl font-semibold text-red-600 mb-2">
171              Something went wrong
172            </h1>
173            <p className="text-gray-600 mb-4">{error.message}</p>
174            <button
175              onClick={resetError}
176              className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
177            >
178              Try Again
179            </button>
180          </div>
181        </div>
182      )}
183    >
184      <UserDashboard userId="123" />
185    </TryErrorBoundary>
186  );
187}

Form with Validation and Submission

A form component that handles validation, submission, and error states using try-error patterns.

Form with Validation Componenttsx
1import { useTryMutation, useTryCallback } from '@try-error/react';
2import { trySync, isTryError } from 'try-error';
3import { useState } from 'react';
4
5interface CreateUserForm {
6  name: string;
7  email: string;
8  password: string;
9  confirmPassword: string;
10}
11
12interface ValidationErrors {
13  name?: string;
14  email?: string;
15  password?: string;
16  confirmPassword?: string;
17}
18
19function CreateUserForm() {
20  const [formData, setFormData] = useState<CreateUserForm>({
21    name: '',
22    email: '',
23    password: '',
24    confirmPassword: '',
25  });
26  const [validationErrors, setValidationErrors] = useState<ValidationErrors>({});
27
28  // Validation function using try-error
29  const validateForm = useTryCallback(
30    (data: CreateUserForm): ValidationErrors => {
31      const errors: ValidationErrors = {};
32
33      // Name validation
34      const nameResult = trySync(() => {
35        if (!data.name.trim()) throw new Error('Name is required');
36        if (data.name.length < 2) throw new Error('Name must be at least 2 characters');
37        return data.name;
38      });
39      if (isTryError(nameResult)) errors.name = nameResult.message;
40
41      // Email validation
42      const emailResult = trySync(() => {
43        if (!data.email.trim()) throw new Error('Email is required');
44        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
45        if (!emailRegex.test(data.email)) throw new Error('Invalid email format');
46        return data.email;
47      });
48      if (isTryError(emailResult)) errors.email = emailResult.message;
49
50      // Password validation
51      const passwordResult = trySync(() => {
52        if (!data.password) throw new Error('Password is required');
53        if (data.password.length < 8) throw new Error('Password must be at least 8 characters');
54        if (!/[A-Z]/.test(data.password)) throw new Error('Password must contain uppercase letter');
55        if (!/[0-9]/.test(data.password)) throw new Error('Password must contain a number');
56        return data.password;
57      });
58      if (isTryError(passwordResult)) errors.password = passwordResult.message;
59
60      // Confirm password validation
61      const confirmResult = trySync(() => {
62        if (data.password !== data.confirmPassword) {
63          throw new Error('Passwords do not match');
64        }
65        return data.confirmPassword;
66      });
67      if (isTryError(confirmResult)) errors.confirmPassword = confirmResult.message;
68
69      return errors;
70    },
71    [formData]
72  );
73
74  // Mutation for creating user
75  const { mutate: createUser, loading, error: submitError, data: createdUser } = useTryMutation(
76    async (userData: Omit<CreateUserForm, 'confirmPassword'>) => {
77      const response = await fetch('/api/users', {
78        method: 'POST',
79        headers: { 'Content-Type': 'application/json' },
80        body: JSON.stringify(userData),
81      });
82      
83      if (!response.ok) {
84        const errorData = await response.json();
85        throw new Error(errorData.message || 'Failed to create user');
86      }
87      
88      return response.json();
89    }
90  );
91
92  const handleSubmit = async (e: React.FormEvent) => {
93    e.preventDefault();
94    
95    // Validate form
96    const errors = validateForm(formData);
97    setValidationErrors(errors);
98    
99    // If validation passes, submit
100    if (Object.keys(errors).length === 0) {
101      const { confirmPassword, ...userData } = formData;
102      await createUser(userData);
103    }
104  };
105
106  const handleInputChange = (field: keyof CreateUserForm) => (
107    e: React.ChangeEvent<HTMLInputElement>
108  ) => {
109    setFormData(prev => ({ ...prev, [field]: e.target.value }));
110    // Clear validation error when user starts typing
111    if (validationErrors[field]) {
112      setValidationErrors(prev => ({ ...prev, [field]: undefined }));
113    }
114  };
115
116  if (createdUser) {
117    return (
118      <div className="max-w-md mx-auto bg-green-50 border border-green-200 rounded-lg p-6">
119        <div className="text-center">
120          <div className="text-green-600 text-4xl mb-4"></div>
121          <h2 className="text-xl font-semibold text-green-800 mb-2">
122            Account Created Successfully!
123          </h2>
124          <p className="text-green-700">
125            Welcome, {createdUser.name}! You can now log in to your account.
126          </p>
127        </div>
128      </div>
129    );
130  }
131
132  return (
133    <div className="max-w-md mx-auto bg-white rounded-lg shadow-md p-6">
134      <h2 className="text-2xl font-bold text-gray-900 mb-6">Create Account</h2>
135      
136      <form onSubmit={handleSubmit} className="space-y-4">
137        <FormField
138          label="Name"
139          type="text"
140          value={formData.name}
141          onChange={handleInputChange('name')}
142          error={validationErrors.name}
143          required
144        />
145        
146        <FormField
147          label="Email"
148          type="email"
149          value={formData.email}
150          onChange={handleInputChange('email')}
151          error={validationErrors.email}
152          required
153        />
154        
155        <FormField
156          label="Password"
157          type="password"
158          value={formData.password}
159          onChange={handleInputChange('password')}
160          error={validationErrors.password}
161          required
162        />
163        
164        <FormField
165          label="Confirm Password"
166          type="password"
167          value={formData.confirmPassword}
168          onChange={handleInputChange('confirmPassword')}
169          error={validationErrors.confirmPassword}
170          required
171        />
172        
173        {submitError && (
174          <div className="bg-red-50 border border-red-200 rounded-lg p-3">
175            <p className="text-red-600 text-sm">{submitError.message}</p>
176          </div>
177        )}
178        
179        <button
180          type="submit"
181          disabled={loading}
182          className="w-full py-2 px-4 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"
183        >
184          {loading ? 'Creating Account...' : 'Create Account'}
185        </button>
186      </form>
187    </div>
188  );
189}
190
191function FormField({
192  label,
193  type,
194  value,
195  onChange,
196  error,
197  required = false,
198}: {
199  label: string;
200  type: string;
201  value: string;
202  onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
203  error?: string;
204  required?: boolean;
205}) {
206  return (
207    <div>
208      <label className="block text-sm font-medium text-gray-700 mb-1">
209        {label} {required && <span className="text-red-500">*</span>}
210      </label>
211      <input
212        type={type}
213        value={value}
214        onChange={onChange}
215        className={`w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 ${
216          error ? 'border-red-300 bg-red-50' : 'border-gray-300'
217        }`}
218        required={required}
219      />
220      {error && (
221        <p className="mt-1 text-sm text-red-600">{error}</p>
222      )}
223    </div>
224  );
225}

Data Fetching with Retry Logic

A component that implements sophisticated retry logic for handling transient failures.

Data Fetcher with Retry Logictsx
1import { useTryAsync } from '@try-error/react';
2import { tryAsync, isTryError } from 'try-error';
3import { useState, useCallback } from 'react';
4
5interface ApiData {
6  id: string;
7  title: string;
8  content: string;
9  lastUpdated: string;
10}
11
12function DataFetcherWithRetry({ endpoint }: { endpoint: string }) {
13  const [retryCount, setRetryCount] = useState(0);
14  const [isRetrying, setIsRetrying] = useState(false);
15
16  const fetchData = useCallback(async (): Promise<ApiData> => {
17    const result = await tryAsync(async () => {
18      const response = await fetch(endpoint);
19      
20      // Handle different types of errors
21      if (response.status === 429) {
22        throw new Error('Rate limited - too many requests');
23      }
24      
25      if (response.status >= 500) {
26        throw new Error(`Server error (${response.status}) - this might be temporary`);
27      }
28      
29      if (!response.ok) {
30        throw new Error(`Request failed with status ${response.status}`);
31      }
32      
33      return response.json();
34    });
35
36    if (isTryError(result)) {
37      throw result;
38    }
39
40    return result;
41  }, [endpoint]);
42
43  const { data, error, loading, refetch } = useTryAsync(fetchData, [fetchData]);
44
45  const handleRetry = async () => {
46    setIsRetrying(true);
47    setRetryCount(prev => prev + 1);
48    
49    // Exponential backoff delay
50    const delay = Math.min(1000 * Math.pow(2, retryCount), 10000);
51    await new Promise(resolve => setTimeout(resolve, delay));
52    
53    refetch();
54    setIsRetrying(false);
55  };
56
57  const isRetriableError = error && (
58    error.message.includes('Rate limited') ||
59    error.message.includes('Server error') ||
60    error.message.includes('Network error')
61  );
62
63  if (loading && !isRetrying) {
64    return (
65      <div className="flex items-center justify-center p-8">
66        <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div>
67        <span className="ml-2 text-gray-600">Loading data...</span>
68      </div>
69    );
70  }
71
72  if (error) {
73    return (
74      <div className="bg-red-50 border border-red-200 rounded-lg p-6">
75        <div className="flex items-start">
76          <div className="flex-shrink-0">
77            <div className="text-red-400 text-xl">⚠️</div>
78          </div>
79          <div className="ml-3 flex-1">
80            <h3 className="text-lg font-medium text-red-800">
81              Failed to load data
82            </h3>
83            <p className="mt-1 text-red-700">{error.message}</p>
84            
85            {error.context && (
86              <details className="mt-2">
87                <summary className="cursor-pointer text-red-600 text-sm">
88                  Error Details
89                </summary>
90                <pre className="mt-1 text-xs bg-red-100 p-2 rounded overflow-auto">
91                  {JSON.stringify(error.context, null, 2)}
92                </pre>
93              </details>
94            )}
95            
96            <div className="mt-4 flex items-center space-x-3">
97              <button
98                onClick={handleRetry}
99                disabled={isRetrying}
100                className="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700 disabled:opacity-50"
101              >
102                {isRetrying ? 'Retrying...' : 'Try Again'}
103              </button>
104              
105              {retryCount > 0 && (
106                <span className="text-sm text-red-600">
107                  Attempt {retryCount + 1}
108                </span>
109              )}
110              
111              {isRetriableError && retryCount < 3 && (
112                <span className="text-sm text-red-600">
113                  This error might be temporary
114                </span>
115              )}
116            </div>
117          </div>
118        </div>
119      </div>
120    );
121  }
122
123  if (!data) {
124    return (
125      <div className="text-center p-8 text-gray-500">
126        No data available
127      </div>
128    );
129  }
130
131  return (
132    <div className="bg-white rounded-lg shadow-md p-6">
133      <div className="flex justify-between items-start mb-4">
134        <h2 className="text-xl font-semibold text-gray-900">{data.title}</h2>
135        <button
136          onClick={() => {
137            setRetryCount(0);
138            refetch();
139          }}
140          className="text-blue-600 hover:text-blue-800 text-sm"
141        >
142          Refresh
143        </button>
144      </div>
145      
146      <div className="prose max-w-none">
147        <p className="text-gray-700">{data.content}</p>
148      </div>
149      
150      <div className="mt-4 text-sm text-gray-500">
151        Last updated: {new Date(data.lastUpdated).toLocaleString()}
152      </div>
153      
154      {retryCount > 0 && (
155        <div className="mt-2 text-xs text-green-600">
156          ✓ Loaded successfully after {retryCount} retry{retryCount > 1 ? 's' : ''}
157        </div>
158      )}
159    </div>
160  );
161}

React Best Practices Summary

✅ Do

  • • Use TryErrorBoundary at the app level for unhandled errors
  • • Handle loading states to improve user experience
  • • Provide retry mechanisms for transient failures
  • • Show different error details in development vs production
  • • Use graceful degradation for non-critical features
  • • Validate user input with try-error for consistent error handling
  • • Clear validation errors when users start correcting them

❌ Don't

  • • Show sensitive error information to end users
  • • Ignore error states in your components
  • • Use generic error messages without context
  • • Forget to handle edge cases like empty data
  • • Block the entire UI for non-critical failures
  • • Retry indefinitely without exponential backoff

Related Pages

React Hooks

Learn about the hooks used in these examples

View Hooks →

React Components

Pre-built components for common patterns

View Components →

Basic Examples

Core try-error patterns and examples

View Basic Examples →