npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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

Readme

@bgscore/react-core

npm version License: MIT typescript

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 hooks
  • useApiSend - Optimized for mutations and POST requests with progress tracking
  • useApiStore - 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 bindApi and wrapApi helpers
  • Event-driven architecture with on/off listeners
  • Detailed lifecycle hooks and callbacks

📦 Installation

npm install @bgscore/react-core
# or
yarn add @bgscore/react-core
# or
pnpm add @bgscore/react-core

Peer 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

  1. Always set __path on API functions for better debugging and caching:

    const getUsers: ApiMethod<...> = (data) => api.post('/users', data);
    getUsers.__path = 'users.list'; // ✅ Important!
  2. Use storeName for global state:

    // Component A
    const [users, opts] = useApiLoad(api, data, { storeName: 'users' });
       
    // Component B - accesses same data
    const [users, opts] = useApiStore(api);
  3. 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
    });
  4. Use useApiSend for mutations, useApiLoad for queries:

    // ❌ Wrong
    const [result, opts] = useApiLoad(createUser, userData);
       
    // ✅ Correct
    const [execute, opts] = useApiSend(createUser);
    await execute(userData);
  5. Handle errors properly:

    const [data, { error, message, response }] = useApiLoad(api, data);
       
    if (error) {
      console.error(`Error ${response?.code}: ${message}`);
    }
  6. 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!