query-harbor
Version:
A React Query utility package for efficient data fetching and caching
350 lines (290 loc) • 9.16 kB
Markdown
# Query Harbor
A collection of custom React hooks built on top of TanStack Query (formerly React Query) for handling API requests, mutations, and cookie management. These hooks provide a standardized way to handle data fetching, caching, and state management in React applications.
## Table of Contents
- [Installation](#installation)
- [Hooks Overview](#hooks-overview)
- [useGlobalQuery](#useglobalquery)
- [useGlobalMutation](#useglobalmutation)
- [useGlobalInfiniteQuery](#useglobalinfinitequery)
- [useCookie](#usecookie)
- [Usage Examples](#usage-examples)
- [API Reference](#api-reference)
## Installation
```bash
# Install required dependencies
npm install @tanstack/react-query react-cookie axios
```
## Hooks Overview
### useGlobalQuery
A custom hook for making standard API requests with built-in caching and state management.
#### Features
- Automatic authentication header handling
- Configurable cache and stale times
- Built-in error handling
- Query invalidation support
#### Basic Usage
```javascript
const {
queryData,
isLoading,
isError,
error,
refetchQuery
} = useGlobalQuery({
url: '/api/users',
queryKey: ['users'],
methodType: 'GET'
});
```
### useGlobalMutation
A custom hook for handling data mutations (create, update, delete operations) with support for FormData.
#### Features
- FormData support with nested object handling
- Automatic query invalidation after successful mutation
- Priority data support
- Built-in error handling
#### Basic Usage
```javascript
const {
runMutation,
mutationLoading,
mutationData,
mutationError,
isMutationSucceeded
} = useGlobalMutation({
url: '/api/users',
queriesToInvalidate: ['users'],
methodType: 'POST',
data: userData
});
```
#### FormData Upload Examples
##### Basic FormData Upload
```javascript
const {
runMutation,
mutationLoading
} = useGlobalMutation({
url: '/api/upload',
queriesToInvalidate: ['files'],
methodType: 'POST',
isFormData: true,
data: {
file: fileObject,
title: 'My Document'
}
});
```
##### Complex FormData with Arrays
```javascript
// Without excludedIndexKeys
const data = {
files: [file1, file2],
metadata: {
titles: ['Doc 1', 'Doc 2']
}
};
// This will generate FormData with structure:
// files[0] = file1
// files[1] = file2
// metadata[titles][0] = Doc 1
// metadata[titles][1] = Doc 2
```
##### Using excludedIndexKeys
```javascript
const MultipleFileUpload = () => {
const {
runMutation,
mutationLoading
} = useGlobalMutation({
url: '/api/upload-multiple',
queriesToInvalidate: ['files'],
methodType: 'POST',
isFormData: true,
// Specify which keys should not include array indices
excludedIndexKeys: ['files', 'documents'],
data: {
files: [file1, file2, file3],
documents: [docFile1, docFile2],
metadata: {
titles: ['Doc 1', 'Doc 2', 'Doc 3'],
categories: ['Cat 1', 'Cat 2', 'Cat 3']
}
}
});
return (
<button onClick={() => runMutation()}>
Upload Files
</button>
);
};
```
The above example will generate FormData with the following structure:
```plaintext
// Keys with excludedIndexKeys:
files = file1
files = file2
files = file3
documents = docFile1
documents = docFile2
// Regular array keys (maintain indices):
metadata[titles][0] = Doc 1
metadata[titles][1] = Doc 2
metadata[titles][2] = Doc 3
metadata[categories][0] = Cat 1
metadata[categories][1] = Cat 2
metadata[categories][2] = Cat 3
```
##### Real-World Example: Multiple File Upload with Metadata
```javascript
const DocumentUploadForm = () => {
const [files, setFiles] = useState([]);
const [metadata, setMetadata] = useState({
department: 'HR',
tags: ['confidential', 'employee'],
});
const {
runMutation,
mutationLoading,
isMutationSucceeded
} = useGlobalMutation({
url: '/api/documents/upload',
queriesToInvalidate: ['documents'],
methodType: 'POST',
isFormData: true,
excludedIndexKeys: ['files'], // files will be sent without indices
data: {
files: files,
metadata: metadata,
timestamp: new Date().toISOString(),
user: {
id: currentUserId,
role: userRole
}
}
});
const handleFileChange = (e) => {
setFiles(Array.from(e.target.files));
};
const handleUpload = () => {
runMutation();
};
return (
<div>
<input
type="file"
multiple
onChange={handleFileChange}
/>
{mutationLoading ? (
<p>Uploading...</p>
) : (
<button onClick={handleUpload}>
Upload Documents
</button>
)}
{isMutationSucceeded && (
<p>Upload completed successfully!</p>
)}
</div>
);
};
```
This will generate FormData where:
- Multiple files are sent with the same key name ('files')
- Metadata is properly nested with array indices preserved
- Additional data is structured appropriately
The resulting FormData structure will be:
```plaintext
files = File1
files = File2
metadata[department] = HR
metadata[tags][0] = confidential
metadata[tags][1] = employee
timestamp = 2024-02-23T10:00:00.000Z
user[id] = 123
user[role] = admin
```
#### When to Use excludedIndexKeys
Use `excludedIndexKeys` when:
1. Working with file upload APIs that expect multiple files with the same key
2. Dealing with legacy APIs that don't support indexed form fields
3. Implementing multi-file upload where the server expects a flat structure
4. Handling file arrays where order doesn't matter
### useGlobalInfiniteQuery
A custom hook for handling infinite scroll or pagination scenarios.
#### Features
- Automatic pagination handling
- Built-in cache management
- Total count tracking
- Next page detection
#### Basic Usage
```javascript
const {
queryData,
isLoading,
isError,
error,
fetchNextPage,
hasNextPage,
totalCount
} = useGlobalInfiniteQuery({
url: '/api/posts',
queryKey: ['posts'],
methodType: 'GET',
data: { limit: 10 }
});
```
### useCookie
A utility hook for managing cookies across the application.
#### Features
- Simple cookie management
- Type-safe cookie operations
- Easy integration with authentication
#### Basic Usage
```javascript
const { cookie, setCookie, removeCookie } = useCookie({
cookieName: 'accessToken'
});
```
## API Reference
### useGlobalQuery Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| url | string | required | API endpoint URL |
| queryKey | string[] | required | Unique key for caching |
| methodType | string | required | HTTP method (GET, POST, etc.) |
| data | object | optional | Request payload |
| enabled | boolean | true | Whether to enable the query |
| cacheTime | number | 300000 | Cache duration in ms |
| staleTime | number | 300000 | Stale data duration in ms |
### useGlobalMutation Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| url | string | required | API endpoint URL |
| queriesToInvalidate | string[] | required | Queries to refresh after mutation |
| methodType | string | required | HTTP method (POST, PUT, DELETE) |
| data | object | optional | Default mutation data |
| isFormData | boolean | false | Whether to handle as FormData |
| closePopup | function | optional | Callback after success |
| excludedIndexKeys | string[] | optional | Keys to exclude from FormData indices |
### useGlobalInfiniteQuery Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| url | string | required | API endpoint URL |
| queryKey | string[] | required | Unique key for caching |
| methodType | string | required | HTTP method |
| data | object | optional | Additional query parameters |
| enabled | boolean | true | Whether to enable the query |
| cacheTime | number | 300000 | Cache duration in ms |
| staleTime | number | 300000 | Stale data duration in ms |
## Best Practices
1. Always provide unique and descriptive query keys
2. Set appropriate cache and stale times based on data volatility
3. Handle loading and error states in your UI
4. Use the refetchQuery function for manual data refresh
5. Implement proper error boundaries in your application
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
## License
MIT