Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Example hook using a Proxy to subscribe to used properties of a store
import { useEffect } from 'react';
/**
* Excercices left to reader to implement:
*
* useResolvedStore
* getValueFromStore
* shouldUpdateForChange
* forceUpdate
* addChangeListener
* removeChangeListener
*/
const BLANK_TARGET = Object.seal({});
const BASE_PROXY_TRAPS = [
'apply',
'construct',
'defineProperty',
'deleteProperty',
'get',
'getOwnPropertyDescriptor',
'getPrototypeOf',
'has',
'isExtensible',
'ownKeys',
'preventExtensions',
'set',
'setPrototypeOf',
].reduce(
(result, key) => ({
...result,
[key]() {
throw new Error('Operation Not Allowed');
},
}),
{},
);
/**
* Use store values and auto-re-render when they change.
* Ex:
* ```js
* // Pass a store directly
* const {loading, items} = useStoreValue(myStore);
* // Select a store from context
* const {loading, items, anyStoreValue} = useStoreValue(store => store.isMyFavoriteStore);
* // Use the nearest store from context
* const {loading, items, sort} = useStoreValue();
* // Also: use computed names and label friendly for local use:
* const {[Store.LOADING]: loading, [Store.SOME_VALUE]: data} = useStoreValue();
* ```
*
* @param {Store|Store[]|function(Store): boolean} [source] A store, an array of stores, or a function to select a store from context. The default resolves the store(s) from context.
* @returns {Record<string, any>} ephemeral store getter proxy. Do not retain a reference to this value. Pull values immediately and discard this proxy.
*/
export function useStoreValue(source = Boolean) {
let locked = false;
const stores = useResolvedStore(source);
const monitoredProperties = new Set();
useEffect(() => {
// prevent reading values after the initial call.
locked = true;
const changed = (change) => {
if (shouldUpdateForChange(change, monitoredProperties)) {
forceUpdate();
}
};
addChangeListener(stores, changed);
return () => {
removeChangeListener(stores, changed);
};
}, [stores]);
return new Proxy(BLANK_TARGET, {
...BASE_PROXY_TRAPS,
get(_, propertyName) {
if (locked) {
throw new Error('Do not store a reference to this intermediate proxy. Get values as properties and discard.');
}
monitoredProperties.add(propertyName);
return getValueFromStore(stores, propertyName);
},
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment