Lightweight and powerful extension for Zustand providing debounced JSON state storage.
Zustand Debounce enhances the capabilities of Zustand by introducing a debounced JSON state storage system. By delaying and grouping write operations to storage, you can significantly reduce the number of write operations, improving performance and efficiency in your applications.
β Ultra Lightweight: Only 1.19 kB gzipped π
β Easy Integration: Seamlessly integrates into your existing projects π
β Customizable Debounce Time: Adjust the debounce time to suit your needs β³
β Reduced Write Operations: Avoid frequent writes to storage, optimizing performance π
β Retry Mechanism: Automatically retries failed write operations with customizable settings π
β
Advanced Retry with Exponential Backoff: Configure exponential backoff with maxRetries
, retryDelay
, and backoffMultiplier
for more resilient retries π
β
Multiple Storage Adapters: Choose between 'localStorage'
, 'sessionStorage'
, or 'memoryStorage'
adapters ποΈ
β
Extended Event System: Additional events like onFlush
, onRetry
, and onError
for better control π£
β TTL Support: Specify a Time-To-Live for stored data β
β Custom Serialization: Use custom serialization and deserialization functions π οΈ
β Full TypeScript Support: Fully typed for TypeScript projects π
# Using npm
npm install zustand-debounce
# Using yarn
yarn add zustand-debounce
# Using pnpm
pnpm add zustand-debounce
To start using Zustand Debounce, replace createJSONStorage with createDebouncedJSONStorage in your Zustand store setup. This will enable delayed writes to your storage.
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { createDebouncedJSONStorage } from 'zustand-debounce';
// Your store interface
interface PersonState {
name: string;
age: number;
// Other state properties
}
interface Actions {
setName: (name: string) => void;
setAge: (age: number) => void;
// Other actions
}
// Create the store
export const usePersonStore = create<PersonState & Actions>()(
persist(
(set) => ({
// Initial state
name: '',
age: 0,
// Actions
setName: (name) => set({ name }),
setAge: (age) => set({ age }),
}),
{
name: 'person-storage',
storage: createDebouncedJSONStorage('localStorage', {
debounceTime: 2000, // Debounce time in milliseconds β³
// Other options can be specified here
}),
}
)
);
With the above setup, changes to the store will be saved to the storage after a 2-second delay, grouping multiple rapid changes into a single write operation.
createDebouncedJSONStorage accepts a variety of options to customize its behavior:
Option | Type | Default | Description |
---|---|---|---|
debounceTime |
number |
0 |
The debounce time in milliseconds. Write operations will be delayed by this amount of time. If multiple writes occur within this period, they will be grouped into a single write. |
throttleTime |
number |
0 |
The throttle time in milliseconds. Ensures that write operations are not performed more frequently than this interval. |
immediately |
boolean |
false |
If set to true , write operations will occur immediately without any delay. |
maxRetries |
number |
0 |
The maximum number of times to retry a failed write operation. |
retryDelay |
number |
0 |
The delay in milliseconds between retry attempts for failed write operations. |
backoffMultiplier |
number |
1 |
The multiplier used for exponential backoff between retry attempts. Each retry will wait retryDelay * (backoffMultiplier ^ attempt) milliseconds. |
onWrite |
(key: string, value: string) => void |
undefined |
A callback function that is called immediately when setItem is invoked, before the debounce delay. |
onSave |
(key: string, value: string) => void |
undefined |
A callback function that is called after the debounce delay when the data is actually saved to storage. |
onFlush |
(key: string, value: string) => void |
undefined |
A callback function that is called when a manual flush operation is executed. |
onRetry |
(key: string, attempt: number, error: any, delay: number) => void |
undefined |
A callback function that is called before each retry attempt, providing information about the retry attempt number, the error that occurred, and the delay before the next attempt. |
onError |
(key: string, error: any) => void |
undefined |
A callback function that is called when all retry attempts have failed. |
serialize |
(state: unknown) => string |
JSON.stringify |
A custom function to serialize the state before saving it to storage. |
deserialize |
(str: string) => unknown |
JSON.parse |
A custom function to deserialize the state after retrieving it from storage. |
ttl |
number |
0 |
Time-to-live in milliseconds for the stored data. After this period, the data will be considered expired and removed from storage. |
Here is an example demonstrating the use of multiple options:
import { createDebouncedJSONStorage } from 'zustand-debounce';
// Example with localStorage
const localStorageExample = createDebouncedJSONStorage('localStorage', {
debounceTime: 1000, // Delay write operations by 1 second
throttleTime: 5000, // Ensure writes are at least 5 seconds apart
immediately: false, // Do not write immediately
maxRetries: 3, // Retry failed writes up to 3 times
retryDelay: 2000, // Wait 2 seconds between retries
ttl: 86400000, // Data expires after 24 hours
onWrite: (key, value) => {
console.log(`Write initiated for ${key}`);
},
onSave: (key, value) => {
console.log(`Data saved for ${key}`);
},
serialize: (state) => {
// Custom serialization logic
return JSON.stringify(state);
},
deserialize: (str) => {
// Custom deserialization logic
return JSON.parse(str);
},
});
// Example with sessionStorage and advanced retry + events
const sessionStorageExample = createDebouncedJSONStorage('sessionStorage', {
debounceTime: 500,
maxRetries: 3,
retryDelay: 1000,
backoffMultiplier: 2, // Each retry will wait longer: 1s, 2s, 4s
onRetry: (key, attempt, error, delay) => {
console.log(`Retry ${attempt} for ${key} after ${delay}ms. Error: ${error.message}`);
},
onError: (key, error) => {
console.error(`All retries failed for ${key}:`, error);
},
onFlush: (key, value) => {
console.log(`Manual flush executed for ${key}`);
}
});
// Example with in-memory storage
const memoryStorageExample = createDebouncedJSONStorage('memoryStorage', {
debounceTime: 100,
immediately: true
});
// Example with custom storage adapter
const customStorageExample = createDebouncedJSONStorage({
getItem: async (key: string) => {
// Implement your custom get logic here
return await myCustomDatabase.get(key);
},
setItem: async (key: string, value: string) => {
// Implement your custom set logic here
await myCustomDatabase.set(key, value);
},
removeItem: async (key: string) => {
// Implement your custom remove logic here
await myCustomDatabase.delete(key);
}
}, {
debounceTime: 1000,
maxRetries: 3,
retryDelay: 1000,
backoffMultiplier: 2
});
You can create your own storage adapter by implementing the StateStorage
interface. This allows you to integrate any storage solution with Zustand Debounce:
interface StateStorage {
getItem: (key: string) => Promise<string | null> | string | null;
setItem: (key: string, value: string) => Promise<void> | void;
removeItem: (key: string) => Promise<void> | void;
}
// Example: Custom IndexedDB adapter
const createIndexedDBAdapter = (dbName: string, storeName: string): StateStorage => {
// Open IndexedDB connection
const dbPromise = indexedDB.open(dbName, 1);
dbPromise.onupgradeneeded = (event) => {
const db = event.target.result;
db.createObjectStore(storeName);
};
return {
async getItem(key) {
const db = await dbPromise;
const transaction = db.transaction(storeName, 'readonly');
const store = transaction.objectStore(storeName);
return await store.get(key);
},
async setItem(key, value) {
const db = await dbPromise;
const transaction = db.transaction(storeName, 'readwrite');
const store = transaction.objectStore(storeName);
await store.put(value, key);
},
async removeItem(key) {
const db = await dbPromise;
const transaction = db.transaction(storeName, 'readwrite');
const store = transaction.objectStore(storeName);
await store.delete(key);
}
};
};
// Use your custom adapter
const customDBStorage = createDebouncedJSONStorage(
createIndexedDBAdapter('myDB', 'zustand-store'),
{
debounceTime: 1000,
// ... other options
}
);
Contributions are welcome! If you have ideas for improvements or have found a bug, please open an issue or submit a pull request.
- Fork the repository
- Create a new branch: git checkout -b feature/your-feature-name
- Make your changes and commit them: git commit -m 'Add some feature'
- Push to the branch: git push origin feature/your-feature-name
- Open a pull request
Please ensure your code follows the project's coding standards and includes appropriate tests.
This project is licensed under the MIT License - see the LICENSE file for details.
If you find this project useful, please consider giving it a β on GitHub. If you have any questions or need support, feel free to open an issue or contact me.
with π by AbianS