Skip to content

Instantly share code, notes, and snippets.

@jsg2021
Last active September 8, 2022 17:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jsg2021/48319e340807ece2c6baac22a3e0c644 to your computer and use it in GitHub Desktop.
Save jsg2021/48319e340807ece2c6baac22a3e0c644 to your computer and use it in GitHub Desktop.
Example hook using a Proxy to subscribe to used properties of a store
import { useEffect } from "react";
/**
* Exercises 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