Skip to main content

Play+ Caching & Storage Helper

Introduction

In the Play+ ecosystem, how we handle client-side data is as important as how we display it. This helper is based on the concept of Intentional and Resilient Storage, providing a safe and intelligent abstraction layer over the browser's raw localStorage API.

Direct use of localStorage is discouraged as it's primitive, error-prone, and insecure for sensitive information. playcache is designed specifically for caching non-sensitive data like user preferences, UI state, and temporary application data. This aligns with our core design pillars by creating an Intuitive API that prevents common errors, an Adaptive system that can handle data expiration, and a Distinct, consistent pattern for storage across all projects.


Package Info

The Play+ storage helper is included by default in the Golden Path starter kit. For existing projects, it can be installed via its dedicated package.

Description

Package / PathDescription
Golden Path (Recommended)Pre-installed (/system/play.cache.ts)
Uplift Pathnpm install @playplus/storage
To get started with the Uplift Path, you can also run:
npx playcache

Folder Reference

File / DirectoryPurpose & Guidelines
system/play.cache.tsThe core storage service. It wraps the localStorage API, providing all helper methods.
config/play.cache.config.jsonAn optional file for overriding default caching behaviors, such as the key prefix.

Helper - Pillars Alignment

PillarHow This Helper Aligns
IntuitivePrimary Pillar: Replaces the error-prone, manual process of using localStorage with a simple, safe, and memorable API.
AdaptiveSupports reactive data patterns through its event system, allowing the UI to adapt to storage changes across tabs.
DistinctEnforces a single, consistent, and safe way to interact with browser storage across all Play+ applications.

Helper Overview

The playcache helper is a smart and resilient wrapper around the browser's localStorage. Its purpose is to abstract the plumbing of client-side caching, so developers can store and retrieve non-sensitive data without worrying about common pitfalls.

It automates and simplifies:

  • Serialization: Automatically runs JSON.stringify() on set and JSON.parse() on get, preventing runtime errors and eliminating boilerplate.
  • Key Scoping: Automatically prefixes all keys with a unique identifier to prevent collisions with other browser tabs, third-party scripts, or other apps on the same domain.
  • Data Expiration (TTL): Supports a "Time-to-Live" for cached items, treating expired data as null.
  • Reactivity: Provides an event-based system (on/off) so your app can react to storage changes, even across tabs.

Config Options

Optional global configuration can be provided in config/play.cache.config.json.

Config VariableDefault ValueDescriptionRecommended Value
keyPrefixplaycacheThe prefix added to all keys to prevent collisions.Keep default
defaultTtlnullDefault Time-to-Live in seconds. null means no expiration.null

Helper Methods

Helper Methods

Method NameDescriptionSignature
setStores a serializable value in the cache, with optional TTL (in seconds).set(key: string, value: any, options?: { ttl: number }): void
getRetrieves a value from the cache. Returns null if key doesn’t exist or has expired.get<T>(key: string): T | null
removeDeletes a specific item from the cache by its key.remove(key: string): void
clearRemoves all items from the cache that have the Play+ key prefix.clear(): void
onSubscribes to changes for a specific key. Callback is triggered on updates (even across tabs).on(key: string, callback: (newValue: any) => void): void
offUnsubscribes a callback for a specific key to prevent memory leaks.off(key: string, callback: (newValue: any) => void): void

Usage Examples

React: A Custom Hook for Reactive Caching

// hooks/usePlayCache.ts
import { useState, useEffect, useCallback } from 'react';
import { playcache } from '../system/play.cache';

export function usePlayCache<T>(key: string, defaultValue: T): [T, (newValue: T) => void] {
const [value, setValue] = useState<T>(() => playcache.get<T>(key) ?? defaultValue);

const updateValue = useCallback((newValue: T) => {
playcache.set(key, newValue);
}, [key]);

useEffect(() => {
const handleUpdate = (newValue: T) => {
setValue(newValue ?? defaultValue);
};
playcache.on(key, handleUpdate);
return () => playcache.off(key, handleUpdate);
}, [key, defaultValue]);

return [value, updateValue];
}
function ThemeSwitcher() {
const [theme, setTheme] = usePlayCache('ui-theme', 'light');
return <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>Current theme: {theme}</button>;
}

Angular: A Reactive Service

// core/services/playcache.service.ts
import { Injectable } from '@angular/core';
import { playcache } from '@playplus/core';
import { Observable, fromEvent, map, startWith } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class PlayCacheService {
observe<T>(key: string, defaultValue: T): Observable<T> {
return fromEvent<StorageEvent>(window, 'storage')
.pipe(map(() => playcache.get<T>(key) ?? defaultValue), startWith(playcache.get<T>(key) ?? defaultValue));
}

set<T>(key: string, value: T, options?: { ttl: number }): void {
playcache.set(key, value, options);
}
}

Additional Info

Why We Created This Helper

Using window.localStorage directly introduces several issues:

  • ❌ Manual JSON serialization/deserialization leads to bugs.
  • ❌ No native TTL or expiration.
  • ❌ Global key collisions.
  • ❌ No reactivity within the same tab.
  • ❌ Encourages storing sensitive data insecurely.

playcache solves these by adding scoping, reactivity, TTLs, and a safe abstraction.

Security: For Non-Sensitive Data Only

playcache does not encrypt data and is not safe for sensitive information. Use playguard for secure token and auth storage via HttpOnly cookies.


Caching Strategies & Best Practices

  • ✅ Cache static reference data (e.g., dropdowns, themes)
  • ✅ Use TTLs to avoid stale state
  • ✅ Clear cache intentionally (e.g., on logout)
  • ❌ Never cache sensitive or dynamic data (e.g., PII, tokens)
  • ❌ Don’t exceed 5MB browser storage limits

Developer Checklist

  • Am I only storing non-sensitive data (like UI preferences or temporary reference data)?
  • For data that can become stale, have I set an appropriate TTL?
  • Am I using playcache instead of localStorage directly?
  • Am I using on() and off() or reactive patterns if my UI needs to respond to changes?
  • Am I clearing or removing cache when appropriate (e.g., logout)?