msc-api-caller-react
v1.1.20
Published
A lightweight React data fetching library inspired by TanStack Query but minimal in scope. Provides simple hooks for API calls with built-in loading states, error handling, and intelligent caching.
Maintainers
Readme
React Microquery
A lightweight React data fetching library inspired by TanStack Query but minimal in scope. Provides simple hooks for API calls with built-in loading states, error handling, and intelligent caching.
Features
- 🚀 Lightweight: ~4KB minified, minimal footprint
- ⚛️ React Hooks: Built with modern React patterns
- 🛡️ TypeScript: Full type safety and inference
- 🔄 Global State: Context-based error and loading management
- 📦 Dual Modules: ESM and CommonJS support
- 🎯 Simple API: Easy to learn and use
- 💾 Smart Caching: Built-in cache with TTL support
- 🚫 Deduplication: Prevents duplicate API calls
- 🧹 Cache Management: Full control over cache operations
- 🔗 Cross-Component Sync: Automatic data synchronization between components
Installation
npm install msc-api-caller-react
# or
yarn add msc-api-caller-react
# or
pnpm add msc-api-caller-reactQuick Start
import {
useApiCaller,
useLazyApiCaller,
APICallerProvider,
useCacheManager,
} from 'msc-api-caller-react';
import { useState } from 'react';
// Example API function
const fetchUser = async (id: number) => {
const response = await fetch(`https://api.example.com/users/${id}`);
return response.json();
};
function App() {
return (
<APICallerProvider
value={{
onError: error => console.error('Global error:', error),
setGlobalLoading: loading => console.log('Global loading:', loading),
debug: true, // Enable debug logging
}}
>
<UserProfile userId={1} />
</APICallerProvider>
);
}
// Automatic fetching on mount
function UserProfile({ userId }: { userId: number }) {
const { data, isLoading, fetch } = useApiCaller(fetchUser, {
args: [userId],
storeKey: 'user-data', // Enable caching
onSuccess: data => console.log('User loaded:', data),
onError: error => console.error('Failed to load user:', error),
});
if (isLoading) return <div>Loading...</div>;
return (
<div>
<h1>{data?.name}</h1>
<p>{data?.email}</p>
<button onClick={() => fetch(userId)}>Refresh</button>
</div>
);
}
// Manual triggering
function SearchUser() {
const [search, setSearch] = useState('');
const [fetchUser, { data, isLoading }] = useLazyApiCaller(fetchUser, {
storeKey: 'search-user',
});
const handleSearch = async () => {
try {
await fetchUser(parseInt(search));
} catch (error) {
console.error('Search failed:', error);
}
};
return (
<div>
<input
value={search}
onChange={e => setSearch(e.target.value)}
placeholder="Enter user ID"
/>
<button onClick={handleSearch} disabled={isLoading}>
{isLoading ? 'Searching...' : 'Search'}
</button>
{data && <div>Found: {data.name}</div>}
</div>
);
}
// Cache management
function CacheControls() {
const { clearCache, removeCache } = useCacheManager();
return (
<div>
<button onClick={() => clearCache()}>Clear All Cache</button>
<button onClick={() => removeCache('user-data_123')}>
Remove Specific Cache
</button>
</div>
);
}API Reference
useApiCaller(api, config)
Automatically fetches data when component mounts (unless skip: true).
Parameters:
api: Function- The async function to callconfig: ApiConfig- Configuration object (optional)
Returns:
{
data: Result | null, // The fetched data
isLoading: boolean, // Loading state
fetch: Function // Manual fetch function
}useLazyApiCaller(api, config)
Returns a fetch function that can be called manually. Does not fetch automatically.
Parameters:
api: Function- The async function to callconfig: ApiConfig- Configuration object (optional)
Returns:
[
fetch: Function, // Manual fetch function
{
data: Result | null, // The fetched data
isLoading: boolean, // Loading state
}
]useCacheManager()
Provides utilities for managing the cache.
Returns:
{
clearCache: () => void; // Clear all cache entries
removeCache: (key: string) => void; // Remove cache entry by exact key
}APICallerProvider
React context provider for global error and loading state management.
Props:
interface ApiCallerConfig {
onError?: (error: Error) => void;
setGlobalLoading?: (loading: boolean) => void;
debug?: boolean;
}ApiConfig
interface ApiConfig<TApi extends FunctionType> {
args?: Parameters<TApi>; // Arguments to pass to API function
onSuccess?: (data, ...args) => void; // Success callback
onError?: (error: Error) => void; // Error callback
onSettled?: () => void; // Called after fetch completes
globalLoading?: boolean; // Use global loading state instead of local
skip?: boolean; // Skip automatic fetching
storeKey?: string; // Cache key for storage
cacheConfig?: CacheConfig; // Custom cache configuration
}CacheConfig
interface CacheConfig {
ttl?: number; // Time to live in milliseconds (default: 5 minutes)
maxSize?: number; // Maximum number of cache entries (global setting)
}Caching System
React Microquery includes a sophisticated caching system:
Features
- TTL Support: Cache entries expire after specified time
- Deduplication: Prevents duplicate API calls for the same request
- Cache Keys: Intelligent cache key generation based on function and parameters
- Size Management: Automatic cache size management with LRU eviction
- Manual Control: Full control over cache operations
Cache Key Format
Cache keys are generated as: {storeKey}_{param1}_{param2}_...
Example Cache Usage
const { data } = useApiCaller(fetchUser, {
args: [123],
storeKey: 'user-profile', // Cache key will be: 'user-profile_123'
cacheConfig: {
ttl: 10 * 60 * 1000, // 10 minutes
},
});Comparison with TanStack Query
| Feature | React Microquery | TanStack Query | | --------------------- | ---------------- | -------------- | | Bundle Size | ~4KB | ~13KB | | Caching | ✅ | ✅ | | Background Refetching | ❌ | ✅ | | Infinite Queries | ❌ | ✅ | | Mutations | ❌ | ✅ | | Devtools | ❌ | ✅ | | TypeScript | ✅ | ✅ | | Global State | ✅ | ✅ | | Error Handling | ✅ | ✅ | | Deduplication | ✅ | ✅ | | Cache Management | ✅ | ✅ |
React Microquery is designed for simple use cases where you need basic data fetching with caching and loading states, without the complexity of background refetching and advanced features.
Cross-Component Synchronization
One of the powerful features of React Microquery is automatic data synchronization between components. When multiple components use the same cache key, they automatically stay synchronized when data changes.
How it works
When Component A and Component B use the same storeKey and parameters, they share the same cache entry. When either component fetches new data, all components using that cache key automatically receive the updated data and re-render.
Example
// Component A - Displays user data automatically
const UserProfile = ({ userId }: { userId: number }) => {
const { data, isLoading } = useApiCaller(fetchUser, {
args: [userId],
storeKey: 'user-profile', // Same cache key
});
if (isLoading) return <div>Loading...</div>;
return (
<div>
<h1>{data?.name}</h1>
<p>{data?.email}</p>
</div>
);
};
// Component B - Can refresh user data
const UserActions = ({ userId }: { userId: number }) => {
const [refreshUser] = useLazyApiCaller(fetchUser, {
storeKey: 'user-profile', // Same cache key
});
const handleRefresh = async () => {
await refreshUser(userId);
// UserProfile component will automatically update!
};
return <button onClick={handleRefresh}>Refresh User</button>;
};
function App() {
return (
<div>
<UserProfile userId={1} />
<UserActions userId={1} />
</div>
);
}Cache Key Format
Cache keys are generated as: {storeKey}_{param1}_{param2}_...
For the example above, both components use:
storeKey: 'user-profile'args: [1]
This creates the cache key: 'user-profile_1'
Both components subscribe to changes for this key and automatically update when the data changes.
Advanced Usage
Error Handling
const MyComponent = () => {
const { data, isLoading, error } = useApiCaller(fetchData, {
onError: err => {
// Local error handling
console.error('API Error:', err);
},
});
if (error) {
return <div>Error: {error.message}</div>;
}
// Component content...
};Global Loading State
function App() {
const [globalLoading, setGlobalLoading] = useState(false);
return (
<APICallerProvider
value={{
setGlobalLoading,
onError: error => console.error('API Error:', error),
}}
>
<MyComponent />
{globalLoading && <GlobalLoadingSpinner />}
</APICallerProvider>
);
}
function MyComponent() {
const { isLoading } = useApiCaller(fetchData, {
globalLoading: true, // Use global loading instead of local
});
// Component content...
}Cache Management
function CacheExample() {
const { clearCache, removeCache } = useCacheManager();
const { data, fetch } = useApiCaller(fetchUser, {
args: [123],
storeKey: 'user-data',
});
const handleRefresh = async () => {
// Remove specific cache entry before refetching
removeCache('user-data_123');
await fetch(123);
};
const handleClearAll = () => {
// Clear all cached data
clearCache();
};
return (
<div>
<button onClick={handleRefresh}>Refresh User</button>
<button onClick={handleClearAll}>Clear All Cache</button>
{/* Component content */}
</div>
);
}Debug Mode
Enable debug logging to see detailed information about API calls, cache hits, and more:
<APICallerProvider value={{ debug: true }}>
<App />
</APICallerProvider>Debug output includes:
- Cache hits and misses
- API call initiation and completion
- Error details
- Cache key generation
🔧 Troubleshooting
Hooks Order Issues
When using React Microquery with React Compiler, you may encounter hooks order errors:
hook.js:608 React has detected a change in the order of Hooks called by null. This will lead to bugs and errors if not fixed.Solution:
Add 'use no memo'; directive at the top of the component file:
'use no memo';
import { useApiCaller } from 'msc-api-caller-react';
const MyComponent = () => {
const { data, isLoading } = useApiCaller(fetchData);
// Component content...
};Common Issues
- Cache Not Working: Ensure
storeKeyis provided in the config - Duplicate Requests: Check if different
storeKeyvalues are being used for the same data - Global Loading Not Working: Ensure
setGlobalLoadingis provided toAPICallerProvider - TypeScript Errors: Make sure your API functions return properly typed promises
TypeScript Support
React Microquery is built with TypeScript and provides excellent type inference:
// API function with proper typing
const fetchUser = async (id: number): Promise<User> => {
const response = await fetch(`/api/users/${id}`);
return response.json();
};
// Hook usage with full type inference
const { data } = useApiCaller(fetchUser, { args: [123] });
// data is automatically typed as User | nullContributing
We welcome contributions! Please feel free to submit a Pull Request.
License
MIT © [Your Name]
