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