@@ -8,16 +8,20 @@ A React hook that provides a seamless way to persist and synchronize state with
88
99## Features
1010
11- - ** ` useState ` Compatible API:** Drop-in replacement with identical API including functional updates
11+ - ** _ useState _ Compatible API:** Drop-in replacement with identical API including functional updates
1212- ** SSR Compatible:** Default values prevent hydration mismatches
1313- ** Auto Synchronization:** Seamless bidirectional sync between React state, ` localStorage ` and across different browser tabs
1414- ** Error handling:** Graceful fallbacks when localStorage operations fail
15+ - ** Custom Encoding/Decoding:** Optional encoder and decoder for data transformation (encryption, compression, etc.)
16+ - ** Dynamic Key Migration:** Automatically migrates data when key changes without data loss
1517
1618::: danger Important Notes
1719
1820- ** Automatic Serialization:** Data is automatically serialized to JSON when storing.
1921- ** Synchronous Updates:** State updates are synchronous and immediately persisted.
2022- ** Fallback value:** Always provide default values for SSR fallback.
23+ - ** Encoder/Decoder:** Applied after JSON serialization and before JSON parsing respectively.
24+ - ** Key Migration:** When key changes, old key is removed and data is migrated to new key automatically.
2125 :::
2226
2327## Problem It Solves
@@ -76,7 +80,7 @@ It's designed to be a drop-in replacement for `useState`, maintaining the famili
7680``` tsx
7781// ✅ Automatic synchronization
7882function UserSettings() {
79- const [theme, setTheme] = useLocalStorage ({ key: ' theme' , defaultValue : ' light' })
83+ const [theme, setTheme] = useLocalStorage ({ key: ' theme' , initialValue : ' light' })
8084
8185 return (
8286 <select value = { theme } onChange = { (e ) => setTheme (e .target .value )} >
@@ -114,7 +118,7 @@ function BrokenComponent() {
114118``` tsx
115119// ✅ Perfect useState compatibility
116120function Component() {
117- const [count, setCount] = useLocalStorage ({ key: ' count' , defaultValue : 0 })
121+ const [count, setCount] = useLocalStorage ({ key: ' count' , initialValue : 0 })
118122
119123 // All familiar useState patterns work perfectly
120124 setCount (5 ) // Direct value
@@ -144,14 +148,14 @@ function ProblematicComponent() {
144148
145149---
146150
147- ** Solution:-** The hook's ` defaultValue ` prop ensures consistent initial renders and smooth hydration by providing predictable fallback values.
151+ ** Solution:-** The hook's ` initialValue ` prop ensures consistent initial renders and smooth hydration by providing predictable fallback values.
148152
149153``` tsx
150154// ✅ SSR-compatible with smooth hydration
151155function SSRFriendlyComponent() {
152156 const [theme, setTheme] = useLocalStorage ({
153157 key: ' theme' ,
154- defaultValue : ' light' , // Used during SSR and as fallback
158+ initialValue : ' light' , // Used during SSR and as fallback
155159 })
156160
157161 // Server and client both start with 'light'
@@ -197,7 +201,7 @@ interface UserSettings {
197201function SafeComponent() {
198202 const [settings, setSettings] = useLocalStorage <UserSettings >({
199203 key: ' user-settings' ,
200- defaultValue : { theme: ' light' , language: ' en' , notifications: true },
204+ initialValue : { theme: ' light' , language: ' en' , notifications: true },
201205 })
202206
203207 // TypeScript ensures settings has the correct shape
@@ -210,12 +214,56 @@ function SafeComponent() {
210214
211215:::
212216
217+ ::: details Lack of Data Security and Transformation
218+
219+ ---
220+
221+ ** Problem:-** Sensitive data stored in localStorage is visible in plain text, and there's no built-in way to transform or compress data before storage.
222+
223+ ``` tsx
224+ // ❌ Sensitive data exposed in plain text
225+ function InsecureComponent() {
226+ const [apiKey, setApiKey] = useLocalStorage ({ key: ' api-key' , initialValue: ' ' })
227+
228+ // API key stored as plain text in localStorage - anyone can read it!
229+ // No way to compress large data structures
230+ }
231+ ```
232+
233+ ---
234+
235+ ** Solution:-** The hook supports optional ` encoder ` and ` decoder ` functions for custom data transformation like encryption, compression, or Base64 encoding.
236+
237+ ``` tsx
238+ // ✅ Encrypted storage with custom encoder/decoder
239+ function SecureComponent() {
240+ const [apiKey, setApiKey] = useLocalStorage ({
241+ key: ' api-key' ,
242+ initialValue: ' ' ,
243+ encoder : (value ) => btoa (value ), // Base64 encode
244+ decoder : (value ) => atob (value ), // Base64 decode
245+ })
246+
247+ // Or use real encryption
248+ const [sensitiveData, setSensitiveData] = useLocalStorage ({
249+ key: ' sensitive' ,
250+ initialValue: {},
251+ encoder : (value ) => encryptData (value ), // Your encryption function
252+ decoder : (value ) => decryptData (value ), // Your decryption function
253+ })
254+ }
255+ ```
256+
257+ :::
258+
213259## Parameters
214260
215- | Parameter | Type | Required | Default Value | Description |
216- | ------------ | :----: | :------: | :-----------: | ----------------------------------------- |
217- | key | string | ✅ | - | Unique key for localStorage item |
218- | defaultValue | any | ❌ | undefined | Initial value when no stored value exists |
261+ | Parameter | Type | Required | Default Value | Description |
262+ | ------------ | :-----------------------: | :------: | :-----------: | ------------------------------------------------------------ |
263+ | key | string | ✅ | - | Unique key for localStorage item |
264+ | initialValue | State \| (() => State) | ❌ | undefined | Initial value when no stored value exists |
265+ | encoder | (value: string) => string | ❌ | undefined | Optional function to encode stringified value before storing |
266+ | decoder | (value: string) => string | ❌ | undefined | Optional function to decode stored value before parsing |
219267
220268## Return Value(s)
221269
@@ -233,6 +281,8 @@ Returns a tuple `[state, setState]` similar to `useState`:
233281- Shopping cart persistence
234282- User settings and preferences
235283- Feature flags for application
284+ - Encrypted/encoded sensitive data storage
285+ - Compressed data for large objects
236286
237287## Usage Examples
238288
@@ -242,8 +292,8 @@ Returns a tuple `[state, setState]` similar to `useState`:
242292import { useLocalStorage } from ' classic-react-hooks'
243293
244294function UserPreferences() {
245- const [theme, setTheme] = useLocalStorage ({ key: ' theme' , defaultValue : ' light' })
246- const [language, setLanguage] = useLocalStorage ({ key: ' language' , defaultValue : ' en' })
295+ const [theme, setTheme] = useLocalStorage ({ key: ' theme' , initialValue : ' light' })
296+ const [language, setLanguage] = useLocalStorage ({ key: ' language' , initialValue : ' en' })
247297
248298 return (
249299 <div >
@@ -279,7 +329,7 @@ interface UserProfile {
279329function ProfileForm() {
280330 const [profile, setProfile] = useLocalStorage <UserProfile >({
281331 key: ' user-profile' ,
282- defaultValue : {
332+ initialValue : {
283333 name: ' ' ,
284334 email: ' ' ,
285335 preferences: {
@@ -327,3 +377,93 @@ function ProfileForm() {
327377```
328378
329379:::
380+
381+ ### Encoded/Encrypted Storage
382+
383+ ::: details
384+
385+ ``` ts
386+ // Base64 encoding example
387+ function Base64Example() {
388+ const [token, setToken] = useLocalStorage ({
389+ key: ' auth-token' ,
390+ initialValue: ' ' ,
391+ encoder : (value ) => btoa (value ), // Encode to Base64
392+ decoder : (value ) => atob (value ), // Decode from Base64
393+ })
394+
395+ return <input type = ' text' value = {token} onChange = {(e ) => setToken (e .target .value )} placeholder = ' Enter token' />
396+ }
397+
398+ // Custom encryption example (pseudo - code )
399+ function EncryptedStorage() {
400+ const encrypt = (value : string ) => {
401+ // Your encryption logic (e.g., AES)
402+ return CryptoJS .AES .encrypt (value , ' secret-key' ).toString ()
403+ }
404+
405+ const decrypt = (value : string ) => {
406+ // Your decryption logic
407+ const bytes = CryptoJS .AES .decrypt (value , ' secret-key' )
408+ return bytes .toString (CryptoJS .enc .Utf8 )
409+ }
410+
411+ const [sensitiveData, setSensitiveData] = useLocalStorage ({
412+ key: ' sensitive-info' ,
413+ initialValue: { apiKey: ' ' , secret: ' ' },
414+ encoder: encrypt ,
415+ decoder: decrypt ,
416+ })
417+
418+ return (
419+ <div >
420+ < input
421+ type = ' password'
422+ value = {sensitiveData.apiKey }
423+ onChange = {(e) => setSensitiveData((prev ) => ({ ... prev , apiKey: e .target .value }))}
424+ placeholder = ' API Key'
425+ / >
426+ < / div >
427+ )
428+ }
429+
430+ // Compression example using pako library
431+ function CompressedStorage() {
432+ const compress = (value : string ) => {
433+ return pako .deflate (value , { to: ' string' })
434+ }
435+
436+ const decompress = (value : string ) => {
437+ return pako .inflate (value , { to: ' string' })
438+ }
439+
440+ const [largeData, setLargeData] = useLocalStorage ({
441+ key: ' large-dataset' ,
442+ initialValue: [],
443+ encoder: compress ,
444+ decoder: decompress ,
445+ })
446+
447+ // Useful for storing large arrays or objects
448+ }
449+ ` ` `
450+
451+ :::
452+
453+ ## Data Flow
454+
455+ The encoding and decoding process follows this flow:
456+
457+ **Storing data:**
458+
459+ ` ` `
460+ State → JSON .stringify () → encoder () → localStorage
461+ ` ` `
462+
463+ **Retrieving data:**
464+
465+ ` ` `
466+ localStorage → decoder () → JSON .parse () → State
467+ ` ` `
468+
469+ Note: The encoder operates on the JSON-stringified value, and the decoder operates before JSON parsing.
0 commit comments