Skip to content

Instantly share code, notes, and snippets.

@Nosherwan
Last active November 1, 2024 06:24
Show Gist options
  • Save Nosherwan/d9e4d1e7eaa746172cc7a571717a2b56 to your computer and use it in GitHub Desktop.
Save Nosherwan/d9e4d1e7eaa746172cc7a571717a2b56 to your computer and use it in GitHub Desktop.
State Management With Proxy
/**
* 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