swd-axios-query
v3.1.7
Published
A React hooks library for easy API requests using Axios and React Query
Maintainers
Readme
SWD Axios Query
A lightweight library that simplifies making API requests in React applications. Combines the power of Axios and React Query for seamless API interactions, caching, and state updates.
Table of Contents
- Features
- Installation
- Quick Start
- Full Example
- Core API Reference
- Optional & Advanced Features
- Contribution & Feedback
Features
| Feature | Description | |-----------------------------------------|------------------------------------------------------------------| | Easy-to-use hooks | For GET, POST, PUT, DELETE requests | | Global Axios/React Query config | Set base URL, headers, and query options | | Query provider | Manages React Query client context | | Custom headers/base URL | Add/override headers and base URL globally | | React Query Devtools support | Enable devtools for debugging | | NEW: Axios interceptors | Add/remove request/response interceptors globally | | NEW: Global loading/error context | Track all API loading/error states with a single provider/hook |
Installation
npm install swd-axios-query
# or
yarn add swd-axios-queryQuick Start
1. Configure the Package
import { configureSWDQuery } from 'swd-axios-query';
configureSWDQuery({
baseURL: 'https://api.example.com',
headers: { Authorization: 'Bearer your_token_here' },
defaultOptions: { retry: 1, refetchOnMount: true },
});2. Wrap Your App
import { SWDQueryProvider } from 'swd-axios-query';
<SWDQueryProvider enableDevTools={true}>
<App />
</SWDQueryProvider>3. Use Hooks in Components
import { useGet } from 'swd-axios-query';
import { useEffect } from 'react';
function Todos() {
const { data, error, isLoading, isSuccess, isError } = useGet(['todos'], '/todos');
// Handle success/error states using useEffect (TanStack Query v5 pattern)
useEffect(() => {
if (isSuccess) {
console.log('Data loaded successfully:', data);
}
}, [isSuccess, data]);
useEffect(() => {
if (isError) {
console.error('Error loading data:', error);
}
}, [isError, error]);
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return <ul>{data?.map(todo => <li key={todo.id}>{todo.title}</li>)}</ul>;
}Full Example
import React from 'react';
import ReactDOM from 'react-dom';
import {
SWDQueryProvider,
SWDGlobalStatusProvider,
configureSWDQuery,
addRequestInterceptor,
addResponseInterceptor,
useGlobalStatus,
useGet
} from 'swd-axios-query';
// Configure once at app startup
configureSWDQuery({ baseURL: 'https://api.example.com' });
// Optional: Add interceptors
addRequestInterceptor(config => {
config.headers.Authorization = 'Bearer ' + localStorage.getItem('token');
return config;
});
addResponseInterceptor(
response => response,
error => {
if (error.response?.status === 401) {
// Handle unauthorized globally
}
return Promise.reject(error);
}
);
function GlobalSpinner() {
const { isLoading } = useGlobalStatus();
return isLoading ? <div className="spinner">Loading...</div> : null;
}
function Todos() {
const { data, isLoading, error, isSuccess, isError } = useGet(['todos'], '/todos');
// Handle success/error states using useEffect (TanStack Query v5 pattern)
useEffect(() => {
if (isSuccess) {
console.log('Todos loaded successfully:', data);
}
}, [isSuccess, data]);
useEffect(() => {
if (isError) {
console.error('Error loading todos:', error);
}
}, [isError, error]);
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return <ul>{data?.map(todo => <li key={todo.id}>{todo.title}</li>)}</ul>;
}
ReactDOM.render(
<SWDQueryProvider>
<SWDGlobalStatusProvider>
<GlobalSpinner />
<Todos />
</SWDGlobalStatusProvider>
</SWDQueryProvider>,
document.getElementById('root')
);Mutation Examples
usePost - Creating Data
import { usePost } from 'swd-axios-query';
import { useEffect } from 'react';
function CreateUser() {
const { mutate, isLoading, error, isSuccess, data } = usePost(
['users'], // Query key for cache invalidation
'/api/users', // API endpoint
{
onSuccess: (data) => {
console.log('User created successfully:', data);
// Optional: Show success message
},
onError: (error) => {
console.error('Failed to create user:', error);
// Optional: Show error message
}
}
);
// Handle success/error states using useEffect (TanStack Query v5 pattern)
useEffect(() => {
if (isSuccess) {
console.log('User created:', data);
// Optional: Redirect or show success message
}
}, [isSuccess, data]);
useEffect(() => {
if (error) {
console.error('Creation failed:', error);
// Optional: Show error message
}
}, [error]);
const handleSubmit = (formData) => {
// Direct data passing - no need to wrap in { data: ... }
mutate({
name: formData.name,
email: formData.email,
age: formData.age
});
};
return (
<form onSubmit={(e) => {
e.preventDefault();
const formData = new FormData(e.target);
handleSubmit({
name: formData.get('name'),
email: formData.get('email'),
age: parseInt(formData.get('age'))
});
}}>
<input name="name" placeholder="Name" required />
<input name="email" type="email" placeholder="Email" required />
<input name="age" type="number" placeholder="Age" required />
<button type="submit" disabled={isLoading}>
{isLoading ? 'Creating...' : 'Create User'}
</button>
{error && <p style={{color: 'red'}}>Error: {error.message}</p>}
</form>
);
}usePut - Updating Data
import { usePut } from 'swd-axios-query';
import { useEffect } from 'react';
function EditUser({ userId, initialData }) {
const { mutate, isLoading, error, isSuccess, data } = usePut(
['users', userId], // Query key for cache invalidation
`/api/users/${userId}`, // API endpoint with ID
{
onSuccess: (data) => {
console.log('User updated successfully:', data);
}
}
);
// Handle success/error states
useEffect(() => {
if (isSuccess) {
console.log('User updated:', data);
// Optional: Show success message or redirect
}
}, [isSuccess, data]);
const handleUpdate = (updatedData) => {
// Direct data passing
mutate({
...initialData,
...updatedData,
updatedAt: new Date().toISOString()
});
};
return (
<form onSubmit={(e) => {
e.preventDefault();
const formData = new FormData(e.target);
handleUpdate({
name: formData.get('name'),
email: formData.get('email'),
age: parseInt(formData.get('age'))
});
}}>
<input
name="name"
defaultValue={initialData.name}
placeholder="Name"
required
/>
<input
name="email"
type="email"
defaultValue={initialData.email}
placeholder="Email"
required
/>
<input
name="age"
type="number"
defaultValue={initialData.age}
placeholder="Age"
required
/>
<button type="submit" disabled={isLoading}>
{isLoading ? 'Updating...' : 'Update User'}
</button>
{error && <p style={{color: 'red'}}>Error: {error.message}</p>}
</form>
);
}useDelete - Deleting Data
import { useDelete } from 'swd-axios-query';
import { useEffect } from 'react';
function DeleteUser({ userId, onDeleted }) {
const { mutate, isLoading, error, isSuccess } = useDelete(
['users', userId], // Query key for cache invalidation
`/api/users/${userId}`, // API endpoint with ID
{
onSuccess: () => {
console.log('User deleted successfully');
// Optional: Call callback or show success message
if (onDeleted) onDeleted();
}
}
);
// Handle success/error states
useEffect(() => {
if (isSuccess) {
console.log('User deleted successfully');
// Optional: Show success message or redirect
}
}, [isSuccess]);
const handleDelete = () => {
if (window.confirm('Are you sure you want to delete this user?')) {
// Simple delete without payload
mutate();
}
};
const handleDeleteWithReason = (reason) => {
if (window.confirm('Are you sure you want to delete this user?')) {
// Delete with optional payload
mutate({
reason: reason,
deletedBy: 'current_user',
deletedAt: new Date().toISOString()
});
}
};
return (
<div>
<button
onClick={handleDelete}
disabled={isLoading}
style={{backgroundColor: 'red', color: 'white'}}
>
{isLoading ? 'Deleting...' : 'Delete User'}
</button>
<button
onClick={() => handleDeleteWithReason('User requested deletion')}
disabled={isLoading}
style={{backgroundColor: 'darkred', color: 'white', marginLeft: '10px'}}
>
{isLoading ? 'Deleting...' : 'Delete with Reason'}
</button>
{error && <p style={{color: 'red'}}>Error: {error.message}</p>}
</div>
);
}useDelete - Bulk Delete with Payload
import { useDelete } from 'swd-axios-query';
function BulkDeleteUsers({ selectedUserIds }) {
const { mutate, isLoading, error, isSuccess } = useDelete(
['users'], // Query key for cache invalidation
'/api/users/bulk-delete', // API endpoint for bulk operations
{
onSuccess: (data) => {
console.log('Bulk delete completed:', data);
// Show success message
}
}
);
const handleBulkDelete = () => {
if (window.confirm(`Delete ${selectedUserIds.length} users?`)) {
// Delete with payload containing IDs and metadata
mutate({
userIds: selectedUserIds,
reason: 'Bulk cleanup',
deletedBy: 'admin',
deletedAt: new Date().toISOString(),
notifyUsers: true
});
}
};
return (
<div>
<button
onClick={handleBulkDelete}
disabled={isLoading || selectedUserIds.length === 0}
style={{backgroundColor: 'red', color: 'white'}}
>
{isLoading ? 'Deleting...' : `Delete ${selectedUserIds.length} Users`}
</button>
{error && <p style={{color: 'red'}}>Error: {error.message}</p>}
{isSuccess && <p style={{color: 'green'}}>Users deleted successfully!</p>}
</div>
);
}Advanced Mutation Example with Custom Headers
import { usePost, setContentTypeHeaders } from 'swd-axios-query';
function FileUpload() {
const { mutate, isLoading, error, isSuccess } = usePost(
['files'],
'/api/upload',
{
// Custom axios options for this specific request
axiosOptions: {
headers: {
'Content-Type': 'multipart/form-data'
},
onUploadProgress: (progressEvent) => {
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
console.log(`Upload Progress: ${percentCompleted}%`);
}
}
}
);
const handleFileUpload = (file) => {
const formData = new FormData();
formData.append('file', file);
formData.append('description', 'Uploaded file');
// Direct data passing
mutate(formData);
};
return (
<div>
<input
type="file"
onChange={(e) => {
const file = e.target.files[0];
if (file) handleFileUpload(file);
}}
disabled={isLoading}
/>
{isLoading && <p>Uploading...</p>}
{isSuccess && <p style={{color: 'green'}}>File uploaded successfully!</p>}
{error && <p style={{color: 'red'}}>Upload failed: {error.message}</p>}
</div>
);
}Mutation with Optimistic Updates
import { usePut, useQueryClient } from 'swd-axios-query';
function ToggleTodo({ todo }) {
const queryClient = useQueryClient();
const { mutate, isLoading } = usePut(
['todos', todo.id],
`/api/todos/${todo.id}`,
{
// Optimistic update
onMutate: async (newData) => {
// Cancel outgoing refetches
await queryClient.cancelQueries({ queryKey: ['todos', todo.id] });
// Snapshot previous value
const previousTodo = queryClient.getQueryData(['todos', todo.id]);
// Optimistically update
queryClient.setQueryData(['todos', todo.id], {
...previousTodo,
...newData
});
return { previousTodo };
},
onError: (err, newData, context) => {
// Rollback on error
queryClient.setQueryData(['todos', todo.id], context.previousTodo);
},
onSettled: () => {
// Refetch after error or success
queryClient.invalidateQueries({ queryKey: ['todos', todo.id] });
}
}
);
const handleToggle = () => {
mutate({
completed: !todo.completed,
updatedAt: new Date().toISOString()
});
};
return (
<button
onClick={handleToggle}
disabled={isLoading}
style={{
textDecoration: todo.completed ? 'line-through' : 'none',
opacity: isLoading ? 0.5 : 1
}}
>
{todo.title}
</button>
);
}Core API Reference
Configuration Functions
configureSWDQuery(config)
Configures the library with custom settings.
- Parameters:
config(object):{ baseURL, headers, defaultOptions, timeout }
- Returns:
void
addHeaders(headers, merge)
Adds additional headers dynamically.
- Parameters:
headers(object): Headers to addmerge(boolean, optional): Whether to merge with existing headers (default: true)
- Returns:
void
setContentTypeHeaders(contentType, additionalHeaders)
Sets default headers for specific content types.
- Parameters:
contentType(string): Content type (e.g., 'application/json', 'multipart/form-data')additionalHeaders(object, optional): Additional headers to set
- Returns:
void
removeHeaders(headerKeys)
Removes specific headers.
- Parameters:
headerKeys(string|Array): Header key(s) to remove
- Returns:
void
getHeaders()
Gets current headers.
- Returns:
Object- Current headers object
axiosInstance
Direct access to the configured Axios instance for custom requests.
- Type: Axios instance
- Usage: For requests outside of React Query hooks
resetToDefaultHeaders()
Resets headers to default values.
- Returns:
void - Default Headers:
Content-Type: application/json,Accept: application/json
SWDQueryProvider
Wrap your app to provide React Query context.
- Props:
children(ReactNode): The application to wrap.queryClient(QueryClient, optional): Custom React Query client.enableDevTools(boolean, optional): Enable React Query Devtools.
Data Hooks
useGet(queryKey, queryUrl, options)usePost(queryKey, queryUrl, options)usePut(queryKey, queryUrl, options)useDelete(queryKey, queryUrl, options)useSuspenseGet(queryKey, queryUrl, options)
Parameters:
queryKey(array): Unique key for the query/mutation.queryUrl(string): API endpoint.options(object, optional): React Query options.
Returns:
- For queries:
{ data, error, isLoading, isSuccess, isError, ... } - For mutations:
{ mutate, mutateAsync, isLoading, error, ... }
Note: This library follows official TanStack Query v5 patterns. Three approaches are supported:
Approach 1: useEffect with Query States (Recommended)
const { data, isSuccess, isError, error } = useGet(['key'], '/api');
useEffect(() => {
if (isSuccess) {
console.log('Success:', data);
}
}, [isSuccess, data]);
useEffect(() => {
if (isError) {
console.error('Error:', error);
}
}, [isError, error]);Approach 2: Global Callbacks via QueryClient
// Set up in your app initialization
const queryClient = new QueryClient({
defaultOptions: {
queries: {
onSuccess: (data, query) => {
if (query.meta?.onSuccess) {
query.meta.onSuccess(data);
}
},
},
},
});Approach 3: meta Option with Global Callbacks
const { data } = useGet(['key'], '/api', {
meta: {
onSuccess: (data) => console.log('Success:', data),
onError: (error) => console.error('Error:', error),
}
});Advanced Usage Examples
Header Management
Default Headers
The library comes with sensible defaults:
- Content-Type:
application/json - Accept:
application/json
These defaults are automatically applied when you import the library. You can override them using the configuration functions.
Basic Header Configuration
import { configureSWDQuery, addHeaders } from 'swd-axios-query';
// Initial configuration
configureSWDQuery({
baseURL: 'https://api.example.com',
headers: {
'Authorization': 'Bearer your-token',
'Content-Type': 'application/json'
}
});
// Add additional headers dynamically
addHeaders({
'X-Custom-Header': 'custom-value',
'Accept': 'application/json'
});Content Type Management
import { setContentTypeHeaders, removeHeaders } from 'swd-axios-query';
// Set headers for file uploads
setContentTypeHeaders('multipart/form-data', {
'X-Upload-Type': 'file'
});
// Set headers for JSON API
setContentTypeHeaders('application/json', {
'Accept': 'application/json'
});
// Remove specific headers
removeHeaders(['X-Custom-Header']);
// or remove multiple
removeHeaders(['X-Custom-Header', 'X-Upload-Type']);File Upload Example
import { setContentTypeHeaders, axiosInstance } from 'swd-axios-query';
function FileUpload() {
const uploadFile = async (file) => {
// Set multipart headers for file upload
setContentTypeHeaders('multipart/form-data');
const formData = new FormData();
formData.append('file', file);
// Use axiosInstance directly for file upload
const response = await axiosInstance.post('/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
});
return response.data;
};
return (
<input
type="file"
onChange={(e) => uploadFile(e.target.files[0])}
/>
);
}Direct Axios Instance Usage
Custom API Requests
import { axiosInstance, addHeaders } from 'swd-axios-query';
// For requests outside of React Query hooks
const customApiCall = async () => {
// Add temporary headers
addHeaders({ 'X-Request-ID': Date.now().toString() });
try {
const response = await axiosInstance.get('/custom-endpoint');
return response.data;
} catch (error) {
console.error('Custom API call failed:', error);
throw error;
}
};
// Batch requests
const batchRequests = async () => {
const [users, posts, comments] = await Promise.all([
axiosInstance.get('/users'),
axiosInstance.get('/posts'),
axiosInstance.get('/comments')
]);
return {
users: users.data,
posts: posts.data,
comments: comments.data
};
};Interceptor Usage
import {
addRequestInterceptor,
addResponseInterceptor,
ejectRequestInterceptor,
ejectResponseInterceptor
} from 'swd-axios-query';
// Add request interceptor
const requestId = addRequestInterceptor((config) => {
config.headers['X-Request-ID'] = Date.now().toString();
return config;
});
// Add response interceptor
const responseId = addResponseInterceptor(
(response) => {
console.log('Response received:', response.status);
return response;
},
(error) => {
console.error('Request failed:', error.message);
return Promise.reject(error);
}
);
// Remove interceptors when needed
ejectRequestInterceptor(requestId);
ejectResponseInterceptor(responseId);Dynamic Header Management
import { addHeaders, getHeaders, removeHeaders } from 'swd-axios-query';
// Check current headers
console.log('Current headers:', getHeaders());
// Add headers conditionally
const addAuthHeader = (token) => {
if (token) {
addHeaders({ 'Authorization': `Bearer ${token}` });
}
};
// Replace all headers (don't merge)
addHeaders({
'Content-Type': 'application/json',
'Accept': 'application/json'
}, false); // merge = false
// Remove authentication header
removeHeaders('Authorization');
// Reset to default headers
resetToDefaultHeaders();Optional & Advanced Features
Axios Request/Response Interceptors
Add or remove Axios interceptors globally for authentication, logging, or error handling.
Add a request interceptor:
import { addRequestInterceptor } from 'swd-axios-query';
const reqId = addRequestInterceptor(config => { /* ... */ return config; });Add a response interceptor:
import { addResponseInterceptor } from 'swd-axios-query';
const resId = addResponseInterceptor(
response => response,
error => Promise.reject(error)
);Remove an interceptor:
import { ejectRequestInterceptor, ejectResponseInterceptor } from 'swd-axios-query';
ejectRequestInterceptor(reqId);
ejectResponseInterceptor(resId);Global Loading/Error State Context (Optional)
Track loading and error state for all API requests in your app with a single provider and hook.
Setup:
import { SWDGlobalStatusProvider } from 'swd-axios-query';
<SWDGlobalStatusProvider>
<App />
</SWDGlobalStatusProvider>Usage:
import { useGlobalStatus } from 'swd-axios-query';
const { isLoading, error } = useGlobalStatus();- This feature is optional. If you don't use the provider, nothing changes in your app.
Contribution & Feedback
We welcome contributions and feedback!
- Report bugs or request features: Open an issue or pull request.
- Questions or suggestions: Contact the maintainer or open a discussion.
- Found a security issue? Please report it privately.
Thank you for using SWD Axios Query!
