This document outlines the way we handle exceptions in Tonomy ID by shown an example under different conditions. Each example is demonstrated with a "do" and "don't" example to show the preferred conventions and common pitfalls to avoid.
- Use
captureError()
for Silently Reporting Errors Users Shouldn’t Deal With - Use the Error Store to Show Unexpected Exceptions to the User
- Handle Network Errors Gracefully
- Try/Catch All Non-Trivial Code
- Always Try/Catch Async Code
- Give Feedback to Users When They Can Fix the Issue
- Use Debug for Logging Important Information
When polling a service or continuously trying to fetch data that will likely succeed later, ensure the operation keeps running without notifying the user unnecessarily.
import { captureError } from '../utils/sentry';
import { useEffect, useState } from 'react';
const MyPriceComponent = () => {
const [price, setPrice] = useState(null);
useEffect(() => {
let isPolling = true;
async function pollPrice() {
while (isPolling) {
try {
const response = await fetch('https://api.example.com/price');
const data = await response.json();
setPrice(data.price);
await sleep(5000); // Poll every 5 seconds
} catch (error) {
captureError('MyComponent() pollPrice()', error); // Log silently
}
}
}
pollPrice();
return () => {
isPolling = false; // Stop polling when component unmounts
};
}, []);
return <div>Current Price: {price ? `$${price}` : 'Loading...'}</div>;
};
When user errors occur, such as password-based private key generation failing, inform the user and log the error.
import useErrorStore from './src/store/errorStore';
const MyComponent = () => {
const { setError } = useErrorStore();
function generatePrivateKey() {
try {
// Simulate private key generation failure
throw new Error('Failed to generate private key from password');
} catch (error) {
setError({ error, expected: false }); // Log and inform the user
}
}
return <button onClick={generatePrivateKey}>Generate Key</button>;
};
When a network request fails due to poor internet, inform the user to retry.
import { isNetworkError, NETWORK_ERROR_RESPONSE } from '../utils/errors';
const MyComponent = () => {
const [errorMessage, setErrorMessage] = useState('');
async function saveUsername(username) {
try {
await user.saveUsername(username); // Simulate API call
} catch (error) {
if (isNetworkError(error)) {
setErrorMessage(NETWORK_ERROR_RESPONSE); // Inform the user
} else {
console.error('Unexpected error:', error);
}
}
}
return (
<div>
<button onClick={() => saveUsername('newUsername')}>Save Username</button>
{errorMessage && <p>{errorMessage}</p>}
</div>
);
};
All complex logic, such as external library calls or operations that may throw, should be wrapped in a try/catch block.
const MyComponent = () => {
const { setError } = useErrorStore();
function processData(data) {
try {
user.storeData(data); // May throw an error
} catch (error) {
setError({ error, expected: false }); // Log unexpected async errors
}
}
return <button onClick={() => processData(null)}>Process Data</button>;
};
Always handle async code to avoid unhandled promise rejections, and use setError()
for consistency.
import useErrorStore from './src/store/errorStore';
const MyComponent = () => {
const { setError } = useErrorStore();
async function fetchData() {
try {
const data = await fetch('https://api.example.com/data');
// Do something with the data
} catch (error) {
setError({ error, expected: false }); // Log unexpected async errors
}
}
return <button onClick={fetchData}>Fetch Data</button>;
};
Provide clear and actionable feedback to users for errors they can resolve, such as filling out a form with required fields.
import React, { useState } from 'react';
const MyComponent = () => {
const [email, setEmail] = useState('');
const [errorMessage, setErrorMessage] = useState('');
const { setError } = useErrorStore();
const handleSubmit = (e) => {
e.preventDefault();
try {
if (!email) {
setErrorMessage('Email is required'); // Show feedback under the field
return;
}
setErrorMessage(''); // Clear error on success
} catch (error) {
setError({ error, expected: false });
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="email">Email:</label>
<input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
{errorMessage && <p style={{ color: 'red' }}>{errorMessage}</p>} {/* Inline feedback */}
</div>
<button type="submit">Submit</button>
</form>
);
};
Use the Debug
utility for logging application events. Do not log sensitive information such as personal information or private/secret keys.
import Debug from 'debug';
const debug = Debug('tonomy-id:MyComponent');
const MyComponent = () => {
debug('MyComponent() rendered');
return <div>Component Loaded</div>;
};
const MyComponent = () => {
console.log('Rendering MyComponent with private key:', privateKey); // Don't log sensitive information
return <div>Component Loaded</div>;
};