@bgscore/react-core
v1.1.10
Published
A React utility library that provides advanced API hooks (fetch, mutation, caching). Built for scalable and flexible data handling in modern React applications.
Downloads
2,043
Maintainers
Readme
@bgscore/react-core
A powerful React utility library providing advanced API hooks, data fetching, caching, and state management capabilities. Built for scalable and flexible data handling in modern React applications.
Perfect for: Data-driven applications, complex API interactions, real-time data updates, and efficient state management.
🌟 Key Features
🚀 Advanced API Hooks
useApiLoad- Comprehensive data fetching with caching, auto-refresh, and lifecycle hooksuseApiSend- Optimized for mutations and POST requests with progress trackinguseApiStore- Global state management for API responses with Zustand integration
📦 Smart Caching System
- Browser Cache API integration with expiry management
- Configurable cache strategies (network-only, cache-first, stale-while-revalidate)
- Persistent or session-based caching
- Automatic cache invalidation
⚡ Performance & Optimization
- Request deduplication and race condition prevention
- AbortController support for cancelling requests
- Automatic refresh on window focus
- Debouncing and throttling utilities
- Data streaming with Server-Sent Events (SSE)
🔐 Security & Encryption
- Built-in request/response encryption support
- Token management and authorization
- Customizable error handling
🎯 Developer Experience
- Full TypeScript support with strong type safety
- Easy API integration with
bindApiandwrapApihelpers - Event-driven architecture with
on/offlisteners - Detailed lifecycle hooks and callbacks
📦 Installation
npm install @bgscore/react-core
# or
yarn add @bgscore/react-core
# or
pnpm add @bgscore/react-corePeer Dependencies
This package requires the following to be installed:
npm install react react-dom axios socket.io-client zustand dayjs🚀 Quick Start
1. Setup BgsCore Provider
Wrap your app with BgsCoreProvider to enable caching and global configuration:
import { BgsCoreProvider } from '@bgscore/react-core';
export default function App() {
return (
<BgsCoreProvider
options={{
cache: {
enabled: true,
strategy: 'stale-while-revalidate',
timeout: { value: 5, unit: 'minutes' },
persistence: false, // session-based cache
},
storageKey: 'app-session', // key for storing auth token
}}
>
<YourApp />
</BgsCoreProvider>
);
}2. Setup API Factory
Create a reusable API factory for your application:
import { useCreateApi } from '@bgscore/react-core';
export function useBaseApi() {
const createApi = useCreateApi();
return createApi({
url: 'https://api.example.com',
token: 'your-auth-token',
headers: { 'Authorization': 'Bearer YOUR_TOKEN' },
withCredentials: true,
encryptRequest: false,
encryptResponse: false,
onUnauthorized: () => {
// Handle unauthorized access
window.location.href = '/login';
},
handleToast: ({ status, message }) => {
// Show success/error messages
console.log(status ? 'Success' : 'Error', message);
},
});
}3. Define Your API Endpoints
import type { ApiMethod } from '@bgscore/react-core';
import { useBaseApi } from './useBaseApi';
interface User {
id: string;
name: string;
email: string;
}
interface UserListRequest {
page: number;
limit: number;
search?: string;
}
export function useUserApi() {
const baseApi = useBaseApi();
const getUsers: ApiMethod<UserListRequest, User[]> = (data, callback, config) => {
return baseApi.post('/users', data, callback, config);
};
getUsers.__path = 'users.list';
const getUserDetail: ApiMethod<string, User> = (userId, callback, config) => {
return baseApi.post(`/users/${userId}`, {}, callback, config);
};
getUserDetail.__path = 'users.detail';
const createUser: ApiMethod<Omit<User, 'id'>, User> = (data, callback, config) => {
return baseApi.post('/users', data, callback, config);
};
createUser.__path = 'users.create';
const updateUser: ApiMethod<User, User> = (data, callback, config) => {
return baseApi.post(`/users/${data.id}`, data, callback, config);
};
updateUser.__path = 'users.update';
return {
getUsers,
getUserDetail,
createUser,
updateUser,
};
}🎯 Core Hooks Documentation
1. useApiLoad - Data Fetching with Caching ⭐⭐⭐
The most comprehensive hook for fetching and caching data.
Basic Usage
import { useApiLoad } from '@bgscore/react-core';
import { useUserApi } from './useUserApi';
function UserList() {
const { getUsers } = useUserApi();
// Fetch users with default settings
const [users, { loading, error, message, refresh }] = useApiLoad(
getUsers,
{ page: 1, limit: 20 }
);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {message}</div>;
return (
<div>
<h1>Users ({users?.length})</h1>
<button onClick={() => refresh()}>Refresh</button>
<ul>
{users?.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}Advanced Usage with Options
function UserDetailView({ userId }: { userId: string }) {
const { getUserDetail } = useUserApi();
const [user, options] = useApiLoad(getUserDetail, userId, {
// Global state management with Zustand
storeName: 'user-detail',
// Caching configuration
cache: {
enabled: true,
strategy: 'stale-while-revalidate',
timeout: 5 * 60, // 5 minutes in seconds
persistence: true, // persist across sessions
cacheName: 'users',
cacheKey: 'custom-key', // custom cache key
},
// Auto-refresh configuration
refreshInterval: { value: 30, unit: 'seconds' },
refetchOnWindowFocus: { threshold: 60 }, // refetch if window was unfocused > 60s
// Lifecycle hooks
beforeRequest: (data) => {
console.log('Before request:', data);
return data; // must return data
},
onBeforeRequest: (data) => {
console.log('API call initiated');
},
afterResponse: (data) => {
// Transform response data
return { ...data, formatted: true };
},
onSuccess: (response, requestData) => {
console.log('Success:', response);
},
onError: (response, requestData) => {
console.error('Error:', response.message);
},
onAfterResponse: (response, requestData) => {
console.log('After response:', response);
},
onChange: (newData, oldData) => {
console.log('Data changed:', { newData, oldData });
},
// Other options
mergeStrategy: 'replace', // or 'append' for pagination
hold: false, // pause auto-refresh
clearPreviousData: false,
logging: true,
});
return (
<div>
<h1>{user?.name}</h1>
<p>Email: {user?.email}</p>
<button onClick={() => options.refresh()}>
Refresh
</button>
<button onClick={() => options.abort()}>
Cancel Request
</button>
<button onClick={() => options.clear()}>
Clear Data
</button>
{options.loading && <p>Loading...</p>}
{options.isCancel && <p>Request cancelled</p>}
</div>
);
}Event-Driven Architecture
function UserListWithEvents() {
const { getUsers } = useUserApi();
const [users, { on }] = useApiLoad(getUsers, { page: 1, limit: 20 });
// Chain multiple event listeners
useEffect(() => {
return on('success', (response) => {
console.log('Data loaded:', response);
})
.on('error', (response) => {
console.error('Failed to load:', response.message);
})
.on('abort', () => {
console.log('Request aborted');
})
.on('clear', () => {
console.log('Data cleared');
})
.on('change', (newData, oldData) => {
console.log('Data changed');
});
}, [on]);
return (
<ul>
{users?.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}Clone API for Multiple Instances
function UserListWithTabs() {
const { getUsers } = useUserApi();
const [activeTab, setActiveTab] = useState<'all' | 'active'>('all');
const [users, options] = useApiLoad(
getUsers,
{ page: 1, limit: 20 },
{ storeName: 'users-list' }
);
// Create a clone for different filters
const activeTabData = options.clone(
{ page: 1, limit: 20, status: 'active' },
{ storeName: 'users-active' }
);
return (
<div>
<div>
<button onClick={() => setActiveTab('all')}>All Users</button>
<button onClick={() => setActiveTab('active')}>Active Only</button>
</div>
{activeTab === 'all' && users?.map(u => <div>{u.name}</div>)}
{activeTab === 'active' && activeTabData[0]?.map(u => <div>{u.name}</div>)}
</div>
);
}Return Value Documentation
const [data, options] = useApiLoad(api, params);
// data: DRes | undefined - The response data
// options object contains:
{
// State
loading: boolean; // Loading state
isCancel: boolean; // Request was cancelled
response: ApiResponse<DRes>; // Full response object
status?: boolean; // API status (success/failure)
message?: string; // API message/error
code?: string | number; // Response code
data?: DRes; // Response data
// Methods
refresh: (forceRefetch?: boolean) => Promise<ApiResponse<DRes>>;
abort: () => void; // Cancel current request
clear: () => void; // Clear data and state
clone: (newPayload, newConfig) => UseCallReturnType; // Clone with new data
// Event Listeners
on: <K extends EventName>(event, callback) => ChainableUnsubscribe;
off: <K extends EventName>(event, callback) => void;
}2. useApiSend - Optimized for Mutations ⭐⭐⭐
Perfect for POST, PUT, PATCH requests with progress tracking.
Basic Mutation
function CreateUserForm() {
const { createUser } = useUserApi();
const [execute, { loading, error, message, progress }] = useApiSend(createUser);
const handleSubmit = async (formData: NewUserData) => {
const response = await execute(formData);
if (response.status) {
console.log('User created:', response.data);
}
};
return (
<form onSubmit={(e) => {
e.preventDefault();
handleSubmit({ name: 'John', email: '[email protected]' });
}}>
<input type="text" placeholder="Name" />
<input type="email" placeholder="Email" />
{loading && <p>Creating... {progress}%</p>}
{error && <p style={{ color: 'red' }}>{message}</p>}
<button type="submit" disabled={loading}>Create</button>
</form>
);
}Advanced Usage with Progress Tracking
function FileUploadForm() {
const { uploadFile } = useApi();
const [execute, options] = useApiSend(uploadFile, {
// Lifecycle callbacks
beforeRequest: (data) => {
console.log('Preparing upload');
return data;
},
onBeforeRequest: (data) => {
console.log('Starting upload');
},
afterResponse: (data) => {
return { ...data, processed: true };
},
onSuccess: (response, data) => {
console.log('Upload successful');
},
onError: (response, data) => {
console.error('Upload failed');
},
onAfterResponse: (response, data) => {
console.log('Upload completed');
},
// Options
logging: true,
abortOnUnmount: true, // cancel request on unmount
});
const handleUpload = async (file: File) => {
const formData = new FormData();
formData.append('file', file);
await execute(formData);
};
return (
<div>
<input
type="file"
onChange={(e) => e.target.files?.[0] && handleUpload(e.target.files[0])}
/>
<div>Upload Progress: {options.progress}%</div>
{options.loading && <p>Uploading...</p>}
<button onClick={() => options.abort()}>Cancel Upload</button>
<button onClick={() => options.reset()}>Reset</button>
</div>
);
}Return Value Documentation
const [execute, options] = useApiSend(api, props);
// execute: (data: DReq) => Promise<ApiResponse<DRes>>
// Call this function to send the request
// options object contains:
{
// State
loading: boolean; // Is request in progress
progress: number; // Upload progress (0-100)
response: ApiResponse; // Full response
status?: boolean; // Success/failure status
message?: string; // Response message
code?: string | number; // Response code
data?: DRes; // Response data
// Methods
abort: () => void; // Cancel current request
reset: () => void; // Reset state and progress
}3. useApiStore - Global State Management ⭐⭐⭐
Access global API state managed by Zustand.
Basic Usage
import { useApiStore } from '@bgscore/react-core';
function UserProfile() {
const { getUsers } = useUserApi();
// Access the global store for getUsers API
const [users, options] = useApiStore(getUsers);
return (
<div>
{options.loading && <p>Loading...</p>}
{users?.map(user => <div key={user.id}>{user.name}</div>)}
</div>
);
}With Custom Store Name
function UserList() {
const { getUsers } = useUserApi();
const [users, options] = useApiStore(getUsers, {
storeName: 'my-users-list', // Custom store name
shallow: true, // Use shallow comparison to prevent unnecessary re-renders
});
return (
<div>
<h1>Users</h1>
{users?.map(user => <div>{user.name}</div>)}
<button onClick={() => options.refresh()}>Refresh</button>
</div>
);
}With Selector for Performance Optimization
function LoadingIndicator() {
const { getUsers } = useUserApi();
// Only subscribe to loading state, not the entire data
const loading = useApiStore(getUsers, {
selector: ([, options]) => options.loading,
});
return loading ? <p>Loading users...</p> : null;
}
function UsersList() {
const { getUsers } = useUserApi();
// Only subscribe to data
const users = useApiStore(getUsers, {
selector: ([data]) => data,
});
return (
<ul>
{users?.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}🛠️ Utility Hooks & Helpers
useDebounce - Debounce Hook
Delay state updates with debouncing.
function SearchUsers() {
const [searchTerm, setSearchTerm] = useState('');
const debouncedTerm = useDebounce(searchTerm, 500); // 500ms delay
const [results, { loading }] = useApiLoad(
getUsers,
{ search: debouncedTerm }
);
return (
<div>
<input
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Search..."
/>
{loading && <p>Searching...</p>}
{results?.map(user => <div key={user.id}>{user.name}</div>)}
</div>
);
}useDelay - Delayed Execution
Execute code after a delay.
function AutoSaveForm() {
const { updateUser } = useUserApi();
const [formData, setFormData] = useState<User | null>(null);
const [, { loading }] = useApiSend(updateUser);
useDelay(async () => {
if (formData) {
await updateUser(formData);
}
}, 2000, [formData]); // Auto-save after 2 seconds of no changes
return (
<form>
<input
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
placeholder="Name"
/>
{loading && <p>Auto-saving...</p>}
</form>
);
}useInterval - Interval Hook
Run code at intervals with automatic cleanup.
function LiveData() {
const [data, { refresh }] = useApiLoad(getLiveData);
useInterval(() => {
refresh(); // Auto-refresh every 10 seconds
}, 10000);
return <div>{data}</div>;
}useStorage - localStorage Hook
Persist and sync state with localStorage.
function UserPreferences() {
const [theme, setTheme] = useStorage('theme', 'light');
const [sidebarOpen, setSidebarOpen] = useStorage('sidebar-open', true);
return (
<div>
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Toggle Theme (Current: {theme})
</button>
<button onClick={() => setSidebarOpen(!sidebarOpen)}>
{sidebarOpen ? 'Close' : 'Open'} Sidebar
</button>
</div>
);
}useStorageTTL - localStorage with Expiry
function SessionData() {
const [sessionId, setSessionId] = useStorageTTL('session-id', '', {
ttl: 24 * 60 * 60 * 1000, // 24 hours
});
return <div>Session: {sessionId}</div>;
}useCrypto - Encryption/Decryption
function SecureData() {
const { encrypt, decrypt } = useCrypto();
const handleEncrypt = (text: string) => {
const encrypted = encrypt(text, 'my-password');
console.log('Encrypted:', encrypted);
};
const handleDecrypt = (encrypted: string) => {
const decrypted = decrypt(encrypted, 'my-password');
console.log('Decrypted:', decrypted);
};
return (
<div>
<button onClick={() => handleEncrypt('secret-data')}>Encrypt</button>
<button onClick={() => handleDecrypt('encrypted-text')}>Decrypt</button>
</div>
);
}useKeyPress - Keyboard Events
Detect key presses easily.
function SearchWithKeyboard() {
const [isOpen, setIsOpen] = useState(false);
useKeyPress('/', () => {
setIsOpen(true);
});
useKeyPress('Escape', () => {
setIsOpen(false);
});
return (
<div>
{isOpen && <input autoFocus placeholder="Search..." />}
<p>Press / to search, Esc to close</p>
</div>
);
}useElementBounding - Element Position & Size
Get element dimensions and position.
function ElementInfo() {
const ref = useRef<HTMLDivElement>(null);
const bounds = useElementBounding(ref);
return (
<div ref={ref}>
<div>Width: {bounds.width}</div>
<div>Height: {bounds.height}</div>
<div>Top: {bounds.top}</div>
<div>Left: {bounds.left}</div>
</div>
);
}useScrollTrigger - Scroll Events
Trigger actions on scroll.
function LazyLoadList() {
const ref = useRef<HTMLDivElement>(null);
const { isTriggered } = useScrollTrigger(ref, { threshold: '80%' });
useEffect(() => {
if (isTriggered) {
loadMoreItems();
}
}, [isTriggered]);
return <div ref={ref}>Load more on scroll...</div>;
}useDynamicSvgImport - SVG Import
function SvgIcon({ name }: { name: string }) {
const { loading, SvgIcon: Icon } = useDynamicSvgImport(
`/icons/${name}.svg`
);
return loading ? <span>Loading...</span> : <Icon />;
}useDbLive - Real-time Database Updates
Subscribe to real-time database changes.
function LiveUserList() {
const [users, options] = useDbLive(
'users', // collection name
{ status: 'active' }, // filter
{
storeName: 'live-users',
onInsert: (item) => console.log('Inserted:', item),
onUpdate: (item) => console.log('Updated:', item),
onDelete: (id) => console.log('Deleted:', id),
}
);
return (
<ul>
{users?.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}useDbLoad - Database Query
Execute database queries.
function UsersByDepartment() {
const [users, options] = useDbLoad({
table: 'users',
filter: { department: 'engineering' },
limit: 50,
});
return (
<div>
{users?.map(user => <div key={user.id}>{user.name}</div>)}
</div>
);
}useApiStream - Server-Sent Events (SSE)
Stream data from server.
function LiveLog() {
const [logs, options] = useApiStream(
'/api/logs/stream',
{
onMessage: (message) => {
console.log('Log:', message);
},
autoConnect: true,
}
);
return (
<div>
<pre>{logs?.join('\n')}</pre>
<button onClick={() => options.connect()}>Connect</button>
<button onClick={() => options.disconnect()}>Disconnect</button>
</div>
);
}🔌 API Integration Helpers
bindApi - Bind Hooks to API Factory
Organize your API endpoints hierarchically.
import { bindApi } from '@bgscore/react-core';
function useApi() {
const baseApi = useBaseApi();
return bindApi({
users: (api) => ({
list: (data) => baseApi.post('/users', data),
detail: (id) => baseApi.post(`/users/${id}`),
create: (data) => baseApi.post('/users', data),
update: (data) => baseApi.post(`/users/${data.id}`, data),
}),
products: (api) => ({
list: (data) => baseApi.post('/products', data),
detail: (id) => baseApi.post(`/products/${id}`),
}),
departments: (api) => ({
list: () => baseApi.post('/departments'),
tree: () => baseApi.post('/departments/tree'),
}),
}, baseApi);
}
// Usage
function App() {
const api = useApi();
// Access nested APIs
const [users] = useApiLoad(api.users.list, { page: 1 });
const [products] = useApiLoad(api.products.list, {});
const [depts] = useApiLoad(api.departments.tree);
}wrapApi - Wrap API Helper
Automatically attach paths to API functions.
import { wrapApi } from '@bgscore/react-core';
const userApi = wrapApi({
getList: (api) => api.post('/users'),
getDetail: (api) => (id: string) => api.post(`/users/${id}`),
create: (api) => (data: any) => api.post('/users', data),
});
// Paths are auto-attached: 'getList', 'getDetail', 'create'🎛️ Configuration & Context
BgsCoreProvider - Global Configuration
import { BgsCoreProvider } from '@bgscore/react-core';
<BgsCoreProvider
options={{
// Caching options
cache: {
enabled: true,
strategy: 'stale-while-revalidate', // 'network-only' | 'cache-first' | 'cache-only'
timeout: { value: 5, unit: 'minutes' }, // or just: 300 (seconds)
persistence: false, // Use localStorage (true) or sessionStorage (false)
},
// Storage key for auth tokens
storageKey: 'auth-token',
// Auto-refresh configuration
refetchOnWindowFocus: {
threshold: 60, // refetch if unfocused > 60 seconds
},
// Clear data when navigating
clearPreviousData: false,
}}
>
<App />
</BgsCoreProvider>useBgsCore - Access Global Config
import { useBgsCore } from '@bgscore/react-core';
function MyComponent() {
const config = useBgsCore();
console.log(config.cache);
console.log(config.storageKey);
console.log(config.refetchOnWindowFocus);
return <div>Config ready</div>;
}🧹 Advanced Features
Request/Response Transformation
const [users, options] = useApiLoad(getUsers, { page: 1 }, {
// Transform request before sending
beforeRequest: (data) => {
return {
...data,
timestamp: Date.now(),
};
},
// Transform response after receiving
afterResponse: (data: User[]) => {
return data.map(user => ({
...user,
displayName: `${user.name} (${user.email})`,
}));
},
});Error Handling
const [data, options] = useApiLoad(api, params, {
onError: (response, requestData) => {
const { status, message, code } = response;
if (code === 401) {
// Handle unauthorized
redirectToLogin();
} else if (code === 403) {
// Handle forbidden
showAccessDenied();
} else if (code === 404) {
// Handle not found
showNotFound();
} else {
// Generic error
showError(message);
}
},
});Data Merging for Pagination
const [allUsers, options] = useApiLoad(
getUsers,
{ page: 1, limit: 20 },
{
storeName: 'paginated-users',
mergeStrategy: 'append', // Append new items instead of replacing
}
);
// Load next page
const loadMore = () => {
options.clone(
{ page: 2, limit: 20 },
{ storeName: 'paginated-users' }
);
};Chained Operations
const [users, options] = useApiLoad(getUsers);
useEffect(() => {
return options
.on('success', (response) => {
console.log('Loaded:', response);
return response; // chain continues
})
.on('change', (newData) => {
console.log('Data updated');
})
.on('error', (err) => {
console.error('Error:', err);
});
}, [options]);📊 Playground Examples
The package includes comprehensive examples in the src/playground directory:
Employee Management API Example
// src/playground/api/employee.api.ts
export default function useEmployeeApi(baseApi: any) {
return {
list: (data) => baseApi.post('/employees', data),
detail: (id) => baseApi.post(`/employees/${id}`),
create: (data) => baseApi.post('/employees', data),
update: (data) => baseApi.post(`/employees/${data.id}`, data),
delete: (id) => baseApi.post(`/employees/${id}/delete`),
};
}Full API Integration
// src/playground/hooks/useBaseApi.ts
// Demonstrates complete API setup with:
// - Token management
// - Request encryption/decryption
// - Error handling with toast notifications
// - Unauthorized access handling
// - CRUD operations (list, create, update, delete, restore)
// - Network error detection🎯 Best Practices
Always set
__pathon API functions for better debugging and caching:const getUsers: ApiMethod<...> = (data) => api.post('/users', data); getUsers.__path = 'users.list'; // ✅ Important!Use
storeNamefor global state:// Component A const [users, opts] = useApiLoad(api, data, { storeName: 'users' }); // Component B - accesses same data const [users, opts] = useApiStore(api);Leverage selectors for performance:
// ❌ Re-renders on any change const [data, options] = useApiStore(api); // ✅ Only re-renders when loading changes const loading = useApiStore(api, { selector: ([, opts]) => opts.loading });Use
useApiSendfor mutations,useApiLoadfor queries:// ❌ Wrong const [result, opts] = useApiLoad(createUser, userData); // ✅ Correct const [execute, opts] = useApiSend(createUser); await execute(userData);Handle errors properly:
const [data, { error, message, response }] = useApiLoad(api, data); if (error) { console.error(`Error ${response?.code}: ${message}`); }Implement proper loading states:
if (loading) return <Skeleton />; if (error) return <ErrorBoundary message={message} />; return <Content data={data} />;
🔍 Troubleshooting
Cache not working
// ❌ Wrong - cache disabled globally
<BgsCoreProvider options={{ cache: { enabled: false } }}>
// ✅ Correct - enable cache
<BgsCoreProvider options={{ cache: { enabled: true, strategy: 'stale-while-revalidate' } }}>State not updating across components
// ❌ Wrong - no global state
const [users, opts] = useApiLoad(api, data);
// ✅ Correct - use storeName for global access
const [users, opts] = useApiLoad(api, data, { storeName: 'users' });
// Now other components can access via
const sameUsers = useApiStore(api, { storeName: 'users' });Infinite re-renders
// ❌ Wrong - object literal recreated each render
const [data] = useApiLoad(api, { page: 1, limit: 20 });
// ✅ Correct - memoize dependency
const params = useMemo(() => ({ page: 1, limit: 20 }), []);
const [data] = useApiLoad(api, params);📚 Type Definitions
Key types exported from the package:
import type {
// Hook types
UseCallReturnType,
UseApiSendReturnType,
OptionsCallReturn,
// API types
ApiMethod,
ApiMethodVoid,
ApiResponse,
// Configuration
UseCallOptionsProps,
BgsCoreProps,
// Utilities
CacheStrategy,
CacheOptions,
} from '@bgscore/react-core';📄 License
MIT License - see LICENSE file for details
🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
📞 Support
For issues, questions, or feature requests, please visit:
Made with ❤️ by Andry Bagus Dharmawan
⭐ If you find this package useful, please star it on GitHub!
