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

@abdokouta/react-cache

v1.2.1

Published

Laravel-inspired caching system with multiple drivers (Memory, Redis, Null) for React

Readme


Table of Contents


Features

  • Multiple Drivers — Memory, Redis, and Null stores out of the box
  • Laravel-Inspired APIremember, rememberForever, pull, tags, and more
  • Cache Tagging — Group related cache entries and flush them together (Redis)
  • React HooksuseCache() and useCachedQuery() for component-level caching
  • Dependency Injection — First-class DI support via @abdokouta/ts-container
  • Type-Safe ConfigurationdefineConfig() helper with full autocomplete
  • TTL Support — Per-operation and per-store default TTL
  • Key Prefixing — Global and per-store prefixes to avoid collisions
  • LRU Eviction — Memory store supports max size with automatic eviction
  • Lazy Initialization — Stores are created on first use
  • Zero Config — Works with sensible defaults, customize when needed

Installation

# npm
npm install @abdokouta/react-cache @abdokouta/ts-container

# pnpm
pnpm add @abdokouta/react-cache @abdokouta/ts-container

# yarn
yarn add @abdokouta/react-cache @abdokouta/ts-container

For Redis support, also install:

pnpm add @abdokouta/react-redis

Quick Start

import { Module } from '@abdokouta/ts-container';
import { CacheModule } from '@abdokouta/react-cache';

@Module({
  imports: [
    CacheModule.forRoot({
      default: 'memory',
      stores: {
        memory: {
          driver: 'memory',
          maxSize: 1000,
          ttl: 300,
        },
      },
    }),
  ],
})
export class AppModule {}

Then use it anywhere via DI or hooks:

import { useCache } from '@abdokouta/react-cache';

function UserProfile({ userId }: { userId: string }) {
  const cache = useCache();

  useEffect(() => {
    async function load() {
      const user = await cache.remember(`user:${userId}`, 3600, () =>
        fetch(`/api/users/${userId}`).then((r) => r.json())
      );
      setUser(user);
    }
    load();
  }, [userId]);

  // ...
}

Configuration

Basic Configuration

The configuration object follows the same structure as Laravel's config/cache.php:

import { CacheModule } from '@abdokouta/react-cache';

CacheModule.forRoot({
  // Default store used when none is specified
  default: 'memory',

  // All available stores
  stores: {
    memory: {
      driver: 'memory',
      maxSize: 1000,
      ttl: 300, // 5 minutes
      prefix: 'mem_',
    },
    redis: {
      driver: 'redis',
      connection: redisClient,
      prefix: 'cache_',
      ttl: 3600, // 1 hour
    },
    null: {
      driver: 'null',
    },
  },

  // Global key prefix (applied to all stores)
  prefix: 'myapp_',
});

Using defineConfig

For type-safe configuration with IDE autocomplete, use the defineConfig helper:

// cache.config.ts
import { defineConfig } from '@abdokouta/react-cache';

export default defineConfig({
  default: 'memory',
  stores: {
    memory: {
      driver: 'memory',
      maxSize: 1000,
      ttl: 300,
    },
    redis: {
      driver: 'redis',
      connection: redisClient,
      prefix: 'cache_',
      ttl: 3600,
    },
  },
  prefix: 'app_',
});

Environment Variables

The included config/cache.config.ts supports Vite environment variables:

| Variable | Description | Default | | ----------------------------- | -------------------------- | ---------- | | VITE_CACHE_DRIVER | Default cache driver | 'memory' | | CACHE_PREFIX | Global key prefix | 'app_' | | VITE_CACHE_MEMORY_MAX_SIZE | Memory store max entries | 1000 | | VITE_CACHE_MEMORY_TTL | Memory store TTL (seconds) | 300 | | VITE_REDIS_CACHE_CONNECTION | Redis connection name | 'cache' | | VITE_CACHE_REDIS_PREFIX | Redis key prefix | 'cache_' | | VITE_CACHE_REDIS_TTL | Redis TTL (seconds) | 3600 |


Cache Drivers

Memory Store

Fast in-memory cache using JavaScript Map. Data is lost on page refresh or process restart.

{
  driver: 'memory',
  maxSize: 1000,   // Max entries before LRU eviction (optional)
  ttl: 300,        // Default TTL in seconds (optional)
  prefix: 'mem_',  // Key prefix (optional)
}

| Option | Type | Default | Description | | --------- | ---------- | ----------- | ---------------------------------- | | driver | 'memory' | — | Required driver identifier | | maxSize | number | undefined | Max entries (unlimited if omitted) | | ttl | number | 300 | Default TTL in seconds | | prefix | string | '' | Key prefix |

Best for: development, client-side caching, temporary data.

Redis Store

Persistent cache backed by Redis. Supports tagging, distributed caching, and atomic operations.

{
  driver: 'redis',
  connection: redisClient,  // RedisConnection instance
  prefix: 'cache_',         // Key prefix (optional)
  ttl: 3600,                // Default TTL in seconds (optional)
}

| Option | Type | Default | Description | | ------------ | ----------------- | ------- | -------------------------- | | driver | 'redis' | — | Required driver identifier | | connection | RedisConnection | — | Redis client instance | | ttl | number | 300 | Default TTL in seconds | | prefix | string | '' | Key prefix |

Best for: production, distributed systems, when persistence or tagging is needed.

Null Store

No-op store that never caches anything. All writes succeed, all reads return undefined.

{
  driver: 'null',
}

Best for: testing, disabling cache, benchmarking without cache overhead.


Usage

Basic Operations

const cache = useCache();

// Store a value (TTL in seconds)
await cache.put('user:123', { name: 'John' }, 3600);

// Retrieve a value
const user = await cache.get('user:123');

// Retrieve with a default
const name = await cache.get('user:name', 'Guest');

// Check existence
if (await cache.has('user:123')) {
  // ...
}

// Store multiple values
await cache.putMany({ 'user:1': user1, 'user:2': user2 }, 3600);

// Retrieve multiple values
const users = await cache.many(['user:1', 'user:2']);

// Store only if key doesn't exist
const added = await cache.add('user:123', user, 3600);

// Store indefinitely (no expiration)
await cache.forever('config:app', appConfig);

// Remove a value
await cache.forget('user:123');

// Get and remove in one call
const token = await cache.pull('auth:token');

// Clear everything
await cache.flush();

Remember Pattern

The remember method retrieves from cache or executes a callback and stores the result — the most common caching pattern:

// Cache for 1 hour, fetch from DB on miss
const user = await cache.remember('user:123', 3600, async () => {
  return await database.users.findById(123);
});

// Cache forever
const config = await cache.rememberForever('app:config', async () => {
  return await loadConfigFromFile();
});

Multiple Stores

Switch between stores at runtime:

const cache = useCache();

// Use default store
await cache.put('key', 'value', 3600);

// Use a specific store
const redisCache = cache.store('redis');
await redisCache.put('key', 'value', 3600);

const memoryCache = cache.store('memory');
await memoryCache.put('key', 'value', 300);

// Or via the hook
const redis = useCache('redis');

Increment & Decrement

Atomic counter operations:

await cache.increment('page:views'); // 1
await cache.increment('page:views', 10); // 11
await cache.decrement('stock:item:42'); // -1
await cache.decrement('stock:item:42', 5); // -6

Cache Tagging

Group related cache entries with tags for bulk invalidation. Tagging is only available with the Redis store.

const cache = useCache('redis');

// Store with tags
await cache.tags(['users', 'premium']).put('user:123', user, 3600);
await cache.tags(['users']).put('user:456', user2, 3600);
await cache.tags(['posts']).put('post:1', post, 3600);

// Retrieve tagged items
const user = await cache.tags(['users', 'premium']).get('user:123');

// Flush all entries tagged with 'users'
await cache.tags(['users']).flush();
// user:123 and user:456 are now gone, post:1 is untouched

// Flush specific tag combination
await cache.tags(['users', 'premium']).flush();

How tagging works under the hood:

  1. Each tag gets a unique namespace ID stored in Redis
  2. Cache keys are prefixed with the combined namespace (e.g., abc123|def456:user:123)
  3. Flushing a tag regenerates its namespace ID, making all old keys inaccessible
  4. Expired entries are tracked in Redis sorted sets for cleanup

React Hooks

useCache

Access the cache service from any React component:

import { useCache } from '@abdokouta/react-cache';

function Dashboard() {
  const cache = useCache();

  const loadStats = async () => {
    return cache.remember('dashboard:stats', 600, async () => {
      const res = await fetch('/api/stats');
      return res.json();
    });
  };

  // ...
}

// Use a specific store
function Widget() {
  const memoryCache = useCache('memory');
  // ...
}

useCachedQuery

A React Query-like hook that caches async query results:

import { useCachedQuery } from '@abdokouta/react-cache';

function UserProfile({ userId }: { userId: string }) {
  const { data, isLoading, error, refetch, invalidate } = useCachedQuery({
    key: `user:${userId}`,
    queryFn: async () => {
      const res = await fetch(`/api/users/${userId}`);
      return res.json();
    },
    ttl: 3600,
  });

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <h1>{data.name}</h1>
      <button onClick={refetch}>Refresh</button>
      <button onClick={invalidate}>Clear Cache & Refresh</button>
    </div>
  );
}

Options

| Option | Type | Default | Description | | ----------------- | ------------------ | ------------- | --------------------------------------- | | key | string | — | Cache key (required) | | queryFn | () => Promise<T> | — | Async function to fetch data (required) | | ttl | number | 300 | TTL in seconds | | storeName | string | default store | Which store to use | | enabled | boolean | true | Enable/disable the query | | refetchOnMount | boolean | false | Force refetch on mount | | refetchInterval | number | — | Auto-refetch interval (ms) |

Return Value

| Property | Type | Description | | ------------ | --------------------- | ---------------------------- | | data | T \| undefined | The cached/fetched data | | isLoading | boolean | Loading state | | error | Error \| null | Error state | | refetch | () => Promise<void> | Re-run query (uses cache) | | invalidate | () => Promise<void> | Clear cache and re-run query |


API Reference

CacheService

The main service providing all cache operations.

| Method | Signature | Description | | --------------------- | ------------------------------------------------------ | -------------------------------- | | get | get<T>(key, defaultValue?): Promise<T \| undefined> | Retrieve a cached value | | many | many<T>(keys): Promise<Record<string, T>> | Retrieve multiple values | | put | put<T>(key, value, ttl?): Promise<boolean> | Store a value | | putMany | putMany<T>(values, ttl?): Promise<boolean> | Store multiple values | | add | add<T>(key, value, ttl?): Promise<boolean> | Store only if key doesn't exist | | has | has(key): Promise<boolean> | Check if key exists | | increment | increment(key, value?): Promise<number> | Increment a numeric value | | decrement | decrement(key, value?): Promise<number> | Decrement a numeric value | | forever | forever<T>(key, value): Promise<boolean> | Store indefinitely | | remember | remember<T>(key, ttl, callback): Promise<T> | Get or compute and store | | rememberForever | rememberForever<T>(key, callback): Promise<T> | Get or compute and store forever | | pull | pull<T>(key, defaultValue?): Promise<T \| undefined> | Get and remove | | forget | forget(key): Promise<boolean> | Remove a value | | flush | flush(): Promise<boolean> | Clear all values | | tags | tags(names): TaggedCache | Get tagged cache (Redis only) | | store | store(name?): CacheService | Switch to a different store | | getDefaultStoreName | getDefaultStoreName(): string | Get default store name | | getStoreNames | getStoreNames(): string[] | List all configured stores | | hasStore | hasStore(name): boolean | Check if store is configured | | getPrefix | getPrefix(): string | Get global key prefix |

Store Interface

All stores implement this interface:

interface Store {
  get(key: string): Promise<any>;
  many(keys: string[]): Promise<Record<string, any>>;
  put(key: string, value: any, seconds: number): Promise<boolean>;
  putMany(values: Record<string, any>, seconds: number): Promise<boolean>;
  increment(key: string, value?: number): Promise<number | boolean>;
  decrement(key: string, value?: number): Promise<number | boolean>;
  forever(key: string, value: any): Promise<boolean>;
  forget(key: string): Promise<boolean>;
  flush(): Promise<boolean>;
  getPrefix(): string;
}

TaggedCache

Returned by cache.tags([...]). Same API as Store with tag-scoped keys:

interface TaggedCache {
  get<T>(key: string): Promise<T | undefined>;
  many(keys: string[]): Promise<Record<string, any>>;
  put(key: string, value: any, ttl?: number): Promise<boolean>;
  putMany(values: Record<string, any>, ttl?: number): Promise<boolean>;
  increment(key: string, value?: number): Promise<number | boolean>;
  decrement(key: string, value?: number): Promise<number | boolean>;
  forever(key: string, value: any): Promise<boolean>;
  forget(key: string): Promise<boolean>;
  flush(): Promise<boolean>;
  getTags(): TagSet;
}

Custom Stores

Implement the Store interface to create your own cache driver:

import type { Store } from '@abdokouta/react-cache';

export class LocalStorageStore implements Store {
  async get(key: string): Promise<any> {
    const item = localStorage.getItem(key);
    return item ? JSON.parse(item) : undefined;
  }

  async put(key: string, value: any, seconds: number): Promise<boolean> {
    localStorage.setItem(
      key,
      JSON.stringify({ value, expires: Date.now() + seconds * 1000 })
    );
    return true;
  }

  // ... implement remaining methods
}

TypeScript

The package is fully typed. All interfaces and types are exported:

import type {
  CacheModuleOptions,
  CacheServiceInterface,
  Store,
  TaggableStore,
  TaggedCache,
  TagSet,
  MemoryStoreConfig,
  RedisStoreConfig,
  NullStoreConfig,
  RedisConnection,
  CacheDriver,
  StoreConfig,
  UseCachedQueryOptions,
  UseCachedQueryResult,
} from '@abdokouta/react-cache';

Laravel Comparison

If you're coming from Laravel, here's how the API maps:

| Laravel | react-cache | | ----------------------------------- | ---------------------------------- | | Cache::get('key') | cache.get('key') | | Cache::put('key', $val, 3600) | cache.put('key', val, 3600) | | Cache::remember('key', 3600, fn) | cache.remember('key', 3600, fn) | | Cache::rememberForever('key', fn) | cache.rememberForever('key', fn) | | Cache::forget('key') | cache.forget('key') | | Cache::pull('key') | cache.pull('key') | | Cache::flush() | cache.flush() | | Cache::has('key') | cache.has('key') | | Cache::add('key', $val, 3600) | cache.add('key', val, 3600) | | Cache::forever('key', $val) | cache.forever('key', val) | | Cache::increment('key') | cache.increment('key') | | Cache::decrement('key') | cache.decrement('key') | | Cache::store('redis') | cache.store('redis') | | Cache::tags(['users'])->get() | cache.tags(['users']).get() | | config/cache.php | defineConfig({...}) |


Requirements

  • Node.js >= 18.0.0
  • React 18 or 19
  • @abdokouta/ts-container (dependency injection)
  • @abdokouta/react-redis (optional, for Redis driver)

License

MIT © Abdelrhman Kouta