
A powerful React hook for HTTP requests with comprehensive callback support, request chaining, and TypeScript integration.
Explore the full documentation, architecture, and deep technical notes for this project on DeepWiki:
npm install use-fetch-with-callbacks
Explore live examples and comprehensive documentation in our Storybook:
Or run locally:
git clone https://github.com/asudbury/use-fetch-with-callbacks.git
cd use-fetch-with-callbacks
npm install
npm run storybook
import useFetchWithCallbacks from 'use-fetch-with-callbacks';
interface User {
id: number;
name: string;
email: string;
}
const UserProfile = () => {
const { response, loading, error, fetchData, postData, deleteData } =
useFetchWithCallbacks<User>('/users/1', {
baseUrl: 'https://api.example.com',
headers: { Authorization: 'Bearer your-token' },
timeout: 5000, // 5 second timeout
});
const handleFetch = () => {
fetchData({
onSuccess: data => console.log('User loaded:', data),
onError: error => console.error('Failed to load user:', error),
onLoading: loading => console.log('Loading state:', loading),
});
};
const handleUpdate = () => {
postData({
data: { name: 'John Doe', email: 'john@example.com' },
onSuccess: data => console.log('User updated:', data),
onError: error => console.error('Update failed:', error),
});
};
const handleDelete = () => {
deleteData({
onSuccess: data => console.log('User deleted:', data),
onError: error => console.error('Delete failed:', error),
});
};
return (
<div>
<button onClick={handleFetch}>Load User</button>
<button onClick={handleUpdate}>Update User</button>
<button onClick={handleDelete}>Delete User</button>
{loading && <p>Loading...</p>}
{error && <p>Error: {error.message}</p>}
{response && <p>Hello, {response.name}!</p>}
</div>
);
};
import React, { useState } from 'react';
import useFetchWithCallbacks from 'use-fetch-with-callbacks';
function App() {
const { loading, error, fetchData } = useFetchWithCallbacks<string>(
'https://jsonplaceholder.typicode.com/users/1'
);
const [firstResponse, setFirstResponse] = useState<string | null>(null);
const [secondResponse, setSecondResponse] = useState<string | null>(null);
const handleSuccess = (data?: string) => {
setFirstResponse(data ?? null);
fetchData({
endpoint: 'https://jsonplaceholder.typicode.com/posts/1',
onSuccess: (data?: string) => {
setSecondResponse(data ?? null);
},
});
};
const handleFetch = () => {
setFirstResponse(null);
setSecondResponse(null);
fetchData({ onSuccess: handleSuccess });
};
return (
<>
<button onClick={handleFetch}>Load Data</button>
{loading && <p>Loading...</p>}
{error && <p>Error: {error.message}</p>}
{firstResponse && (
<>
<hr />
<div>1st response</div>
<pre>{JSON.stringify(firstResponse, null, 2)}</pre>
</>
)}
{secondResponse && (
<>
<hr />
<div>2nd response</div>
<pre>{JSON.stringify(secondResponse, null, 2)}</pre>
</>
)}
</>
);
}
RequestParams<T>The RequestParams<T> type is used for all request methods (GET, POST, PUT, DELETE, PATCH) and for chainable operations. It allows you to specify the endpoint, request body, and callbacks for handling success, error, and loading states.
type RequestParams<T> = {
endpoint?: string;
data?: unknown;
onSuccess?: (data: T) => void;
onError?: (error: Error) => void;
onLoading?: (loading: boolean) => void;
};
Properties:
endpoint (string, optional): Override the default endpoint for this request.data (unknown, optional): Data to send in the request body (for POST, PUT, PATCH).onSuccess ((data: T) => void, optional): Callback executed when the request succeeds.onError ((error: Error) => void, optional): Callback executed when the request fails.onLoading ((loading: boolean) => void, optional): Callback executed when loading state changes.Example:
fetchData({
endpoint: '/users/1',
onSuccess: user => console.log('Loaded user:', user),
onError: error => console.error('Failed:', error),
onLoading: loading => console.log('Loading:', loading),
});
postData({
endpoint: '/users',
data: { name: 'Jane' },
onSuccess: user => console.log('Created:', user),
});
Note: Each chainable method (fetch, post, put, patch, delete) now accepts an optional endpoint as the first argument, allowing you to chain requests to different endpoints in a single workflow.
const Dashboard = () => {
const { fetchMultipleData } = useFetchWithCallbacks<any>('/', {
baseUrl: 'https://api.example.com',
});
const loadDashboard = () => {
fetchMultipleData({
endpoints: ['/users', '/posts', '/comments'],
onSuccess: results => {
console.log('All data loaded:', results);
},
onError: error => console.error('Failed to load dashboard:', error),
});
};
return <button onClick={loadDashboard}>Load Dashboard</button>;
};
const api = useFetchWithCallbacks<ApiResponse>('/data', {
baseUrl: 'https://api.example.com',
headers: {
Authorization: 'Bearer token',
'Content-Type': 'application/json',
'X-Custom-Header': 'value',
},
timeout: 10000, // 10 second timeout
});
useFetchWithCallbacks<T>(endpoint, options?)Parameters:
endpoint (string): The API endpoint pathoptions (UseFetchOptions): Optional configurationReturns: FetchResult<T>
FetchResult<T>interface FetchResult<T> {
response: T | null;
loading: boolean;
error: Error | null;
requestCompleted: boolean;
fetchData: (...) => Promise<void>;
postData: (...) => Promise<void>;
putData: (...) => Promise<void>;
deleteData: (...) => Promise<void>;
patchData: (...) => Promise<void>;
fetchMultipleData: (...) => Promise<void>;
chain: () => ChainableRequest<T>;
}
UseFetchOptionsinterface UseFetchOptions {
baseUrl?: string; // Base URL for all requests
headers?: HeadersInit; // Default headers
timeout?: number; // Request timeout (default: 10000ms)
}
RequestParams<T>The RequestParams<T> type is used for all request methods (GET, POST, PUT, DELETE, PATCH) and for chainable operations. It allows you to specify the endpoint, request body, and callbacks for handling success, error, and loading states.
type RequestParams<T> = {
endpoint?: string; // Optional
data?: unknown; // Optional
onSuccess?: (data: T) => void; // Optional
onError?: (error: Error) => void; // Optional
onLoading?: (loading: boolean) => void; // Optional
};
Properties:
endpoint (string, optional): Override the default endpoint for this request.data (unknown, optional): Data to send in the request body (for POST, PUT, PATCH).onSuccess ((data: T) => void, optional): Callback executed when the request succeeds.onError ((error: Error) => void, optional): Callback executed when the request fails.onLoading ((loading: boolean) => void, optional): Callback executed when loading state changes.Example:
fetchData({
endpoint: '/users/1',
onSuccess: user => console.log('Loaded user:', user),
onError: error => console.error('Failed:', error),
onLoading: loading => console.log('Loading:', loading),
});
postData({
endpoint: '/users',
data: { name: 'Jane' },
onSuccess: user => console.log('Created:', user),
});
ChainableRequest<T>The chain API allows you to compose and execute multiple requests in sequence, with full callback support for each step and for the overall chain. Each chainable method can override the endpoint and provide per-request callbacks.
Available methods:
fetch(params?: RequestParams<T>)post(params: RequestParams<T>)put(params: RequestParams<T>)delete(params?: RequestParams<T>)patch(params: RequestParams<T>)then(callback: (data: T) => void)catch(callback: (error: Error) => void)finally(callback: () => void)execute()Example:
const { chain } = useFetchWithCallbacks<User>('/users/1', {
baseUrl: 'https://api.example.com',
});
chain()
.fetch({
endpoint: '/users/1',
onSuccess: user => console.log('Loaded user:', user),
})
.put({
endpoint: '/users/1',
data: { name: 'Updated Name' },
onSuccess: user => console.log('Updated user:', user),
})
.then(user => {
// Called after the last successful request in the chain
console.log('Chain completed. Final user:', user);
})
.catch(error => {
// Called if any request in the chain fails
console.error('Chain failed:', error);
})
.finally(() => {
// Always called at the end
console.log('Chain finished');
})
.execute();
Tip: Each chainable method (
fetch,post,put,patch,delete) accepts an optionalendpointand per-request callbacks, allowing you to build flexible, readable workflows.
The hook provides comprehensive error handling:
const { fetchData } = useFetchWithCallbacks<User>('/users/1');
fetchData(
data => {
console.log('Success:', data);
},
error => {
if (error.message === 'Request timeout') {
console.log('Request timed out');
} else if (error.message.includes('404')) {
console.log('User not found');
} else {
console.log('Other error:', error.message);
}
}
);
Requests are automatically cancelled when:
const { fetchData } = useFetchWithCallbacks<User>('/users/1');
// This request will be cancelled if component unmounts
fetchData(
data => console.log('Success:', data),
error => console.log('Error:', error)
);
Full TypeScript support with proper type inference:
interface User {
id: number;
name: string;
email: string;
}
// T is automatically inferred as User
const { response, fetchData } = useFetchWithCallbacks<User>('/users/1');
MIT © Adrian Sudbury
Contributions are welcome! Please read our Contributing Guide to learn about our development process, coding standards, and how to submit pull requests.
If you have any questions or need help, please Open an issue or use the links above.
Made with ❤️ for the React community