Last active
November 1, 2024 06:24
-
-
Save Nosherwan/d9e4d1e7eaa746172cc7a571717a2b56 to your computer and use it in GitHub Desktop.
State Management With Proxy
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Creates a state management system using JavaScript Proxies | |
* Includes support for batched updates to optimize performance | |
*/ | |
function createStateManager(initialState = {}) { | |
const listeners = new Set(); | |
let batchingEnabled = false; | |
let batchedChanges = new Map(); // Store changes during batch updates | |
/** | |
* Notifies all subscribers of state changes | |
* Handles both individual and batched updates | |
*/ | |
const notifyListeners = (path, value, previousValue) => { | |
if (batchingEnabled) { | |
// Store change for later notification | |
batchedChanges.set(path, { value, previousValue }); | |
} else { | |
// Immediate notification | |
listeners.forEach(listener => | |
listener({ path, value, previousValue }) | |
); | |
} | |
}; | |
const createDeepProxy = (path = '') => ({ | |
get(target, property) { | |
if (typeof target[property] === 'object' && target[property] !== null) { | |
return new Proxy( | |
target[property], | |
createDeepProxy(`${path}${path ? '.' : ''}${property}`) | |
); | |
} | |
return target[property]; | |
}, | |
set(target, property, value) { | |
const previousValue = target[property]; | |
target[property] = value; | |
notifyListeners( | |
`${path}${path ? '.' : ''}${property}`, | |
value, | |
previousValue | |
); | |
return true; | |
}, | |
deleteProperty(target, property) { | |
const previousValue = target[property]; | |
delete target[property]; | |
notifyListeners( | |
`${path}${path ? '.' : ''}${property}`, | |
undefined, | |
previousValue | |
); | |
return true; | |
} | |
}); | |
const state = new Proxy(initialState, createDeepProxy()); | |
/** | |
* Executes multiple state updates in a batch | |
* Subscribers are only notified once after all updates complete | |
* | |
* @param {Function} updateFn - Function containing multiple state updates | |
*/ | |
const batch = (updateFn) => { | |
// Enable batching mode | |
batchingEnabled = true; | |
batchedChanges.clear(); | |
try { | |
// Execute the updates | |
updateFn(); | |
// Notify listeners of all changes at once | |
if (batchedChanges.size > 0) { | |
listeners.forEach(listener => { | |
// Provide all changes as a single batch | |
listener({ | |
type: 'batch', | |
changes: Array.from(batchedChanges.entries()).map(([path, change]) => ({ | |
path, | |
...change | |
})) | |
}); | |
}); | |
} | |
} finally { | |
// Always disable batching and clear changes, even if there's an error | |
batchingEnabled = false; | |
batchedChanges.clear(); | |
} | |
}; | |
return { | |
getState: () => state, | |
subscribe: (callback) => { | |
listeners.add(callback); | |
return () => listeners.delete(callback); | |
}, | |
select: (selector) => selector(state), | |
reset: () => { | |
Object.keys(state).forEach(key => { | |
delete state[key]; | |
}); | |
Object.assign(state, initialState); | |
}, | |
batch // Expose batch method in public interface | |
}; | |
} | |
// Example usage with batched updates | |
const userState = createStateManager({ | |
user: { | |
profile: { | |
name: 'John Doe', | |
email: '[email protected]', | |
preferences: { | |
theme: 'dark', | |
notifications: true, | |
fontSize: 14, | |
language: 'en' | |
} | |
}, | |
statistics: { | |
lastLogin: null, | |
loginCount: 0, | |
lastActive: null | |
} | |
} | |
}); | |
// Subscribe to state changes | |
const unsubscribe = userState.subscribe((update) => { | |
if (update.type === 'batch') { | |
console.log('Batch update occurred:', update.changes); | |
// Example output: | |
// Batch update occurred: [ | |
// { path: 'user.profile.preferences.theme', value: 'light', previousValue: 'dark' }, | |
// { path: 'user.profile.preferences.fontSize', value: 16, previousValue: 14 }, | |
// { path: 'user.statistics.lastLogin', value: '2024-11-01', previousValue: null }, | |
// { path: 'user.statistics.loginCount', value: 1, previousValue: 0 } | |
// ] | |
} else { | |
console.log('Single update:', update); | |
} | |
}); | |
// Example of individual updates (will trigger multiple notifications) | |
const state = userState.getState(); | |
state.user.profile.preferences.theme = 'light'; // Triggers notification | |
state.user.profile.preferences.fontSize = 16; // Triggers notification | |
// Example of batched updates (will trigger only one notification) | |
userState.batch(() => { | |
const state = userState.getState(); | |
// Update multiple properties at once | |
state.user.profile.preferences.theme = 'light'; | |
state.user.profile.preferences.fontSize = 16; | |
state.user.statistics.lastLogin = '2024-11-01'; | |
state.user.statistics.loginCount++; | |
}); | |
// Example of using batch with async operations | |
async function updateUserSettings(newSettings) { | |
userState.batch(() => { | |
const state = userState.getState(); | |
Object.entries(newSettings).forEach(([key, value]) => { | |
state.user.profile.preferences[key] = value; | |
}); | |
state.user.statistics.lastActive = new Date().toISOString(); | |
}); | |
} | |
// Clean up | |
unsubscribe(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment