@preact-signals/safe-react
Version:
Manage state with style in React
1 lines • 11.7 kB
Source Map (JSON)
{"version":3,"file":"tracking.mjs","sources":["../../../src/lib/tracking.ts"],"sourcesContent":["import { effect, Signal } from \"@preact/signals-core\";\nimport { useRef, version } from \"react\";\nimport { useSyncExternalStore } from \"use-sync-external-store/shim/index.js\";\n\nconst ReactElemType = Symbol.for(\n parseInt(version) >= 19 ? \"react.transitional.element\" : \"react.element\",\n); // https://github.com/facebook/react/blob/346c7d4c43a0717302d446da9e7423a8e28d8996/packages/shared/ReactSymbols.js#L15\n\nconst symDispose: unique symbol =\n (Symbol as any).dispose || Symbol.for(\"Symbol.dispose\");\n\n// this is effect before mangling, since we are not in preact signals repo, we should use mangled props\n// interface Effect {\n// _sources: object | undefined;\n// _start(): () => void;\n// _callback(): void;\n// _dispose(): void;\n// }\nconst enum EffectFields {\n startTracking = \"S\",\n onDepsChange = \"c\",\n dispose = \"d\",\n}\ninterface Effect {\n [EffectFields.startTracking](): () => void;\n [EffectFields.onDepsChange](): void;\n [EffectFields.dispose](): void;\n}\n\nconst enum EffectStoreFields {\n startTracking = \"s\",\n finishTracking = \"f\",\n resetSyncRerenders = \"r\",\n}\n\nexport interface EffectStore {\n effect: Effect;\n subscribe(onStoreChange: () => void): () => void;\n getSnapshot(): number;\n /** finishEffect - stop tracking the signals used in this component */\n [EffectStoreFields.finishTracking](): void;\n [EffectStoreFields.startTracking](): void;\n [EffectStoreFields.resetSyncRerenders](): void;\n\n [symDispose](): void;\n}\n\nconst _queueMicrotask = Promise.prototype.then.bind(Promise.resolve());\nconst resetSyncRerendersSet = new Set<EffectStore>();\nlet isResetSyncRerendersScheduled = false;\nconst resetSyncRerenders = () => {\n isResetSyncRerendersScheduled = false;\n resetSyncRerendersSet.forEach((store) => {\n store[EffectStoreFields.resetSyncRerenders]();\n });\n resetSyncRerendersSet.clear();\n};\nconst scheduleResetSyncRerenders = (store: EffectStore) => {\n if (!isResetSyncRerendersScheduled) {\n isResetSyncRerendersScheduled = true;\n void _queueMicrotask(resetSyncRerenders);\n }\n if (!resetSyncRerendersSet.has(store)) {\n resetSyncRerendersSet.add(store);\n }\n};\n\nlet useSignalsDepth = 0;\nlet cleanUpFn: (() => void) | undefined = undefined;\nconst maxSyncRerenders = 25;\n/**\n * A redux-like store whose store value is a positive 32bit integer (a 'version').\n *\n * React subscribes to this store and gets a snapshot of the current 'version',\n * whenever the 'version' changes, we tell React it's time to update the component (call 'onStoreChange').\n *\n * How we achieve this is by creating a binding with an 'effect', when the `effect._callback' is called,\n * we update our store version and tell React to re-render the component ([1] We don't really care when/how React does it).\n *\n * [1]\n * @see https://react.dev/reference/react/useSyncExternalStore\n * @see https://github.com/reactjs/rfcs/blob/main/text/0214-use-sync-external-store.md\n */\nfunction createEffectStore(): EffectStore {\n let effectInstance!: Effect;\n let version = 0;\n let onChangeNotifyReact: (() => void) | undefined;\n\n let unsubscribe = effect(function (this: Effect) {\n effectInstance = this;\n });\n let inRender = false;\n let syncRerendersCount = 0;\n effectInstance[EffectFields.onDepsChange] = function () {\n if (inRender) {\n return;\n }\n if (syncRerendersCount > maxSyncRerenders) {\n throw new Error(\n `preact-signals: Too many sync rerenders (${syncRerendersCount}), you might change parent component signal dependencies in render of child component.`,\n );\n }\n version = (version + 1) | 0;\n if (!onChangeNotifyReact) {\n return;\n }\n\n // react throws here sometimes\n onChangeNotifyReact();\n };\n\n return {\n effect: effectInstance,\n subscribe(onStoreChange) {\n onChangeNotifyReact = onStoreChange;\n\n return function () {\n /**\n * Rotate to next version when unsubscribing to ensure that components are re-run\n * when subscribing again.\n *\n * In StrictMode, 'memo'-ed components seem to keep a stale snapshot version, so\n * don't re-run after subscribing again if the version is the same as last time.\n *\n * Because we unsubscribe from the effect, the version may not change. We simply\n * set a new initial version in case of stale snapshots here.\n */\n version = (version + 1) | 0;\n onChangeNotifyReact = undefined;\n unsubscribe();\n };\n },\n [EffectStoreFields.resetSyncRerenders]() {\n syncRerendersCount = 0;\n },\n [EffectStoreFields.startTracking]() {\n inRender = true;\n syncRerendersCount++;\n scheduleResetSyncRerenders(this);\n if (!useSignalsDepth && cleanUpFn) {\n throw new Error(\"cleanUpFn should be undefined\");\n }\n if (useSignalsDepth && !cleanUpFn) {\n throw new Error(\"cleanUpFn should be defined with depth\");\n }\n if (!useSignalsDepth) {\n cleanUpFn = effectInstance[EffectFields.startTracking]();\n }\n useSignalsDepth++;\n },\n getSnapshot() {\n return version;\n },\n [EffectStoreFields.finishTracking]() {\n if (useSignalsDepth < 1) {\n throw new Error(\"useSignalsDepth should be non-negative\");\n }\n try {\n if (useSignalsDepth === 1 && !cleanUpFn) {\n throw new Error(\"cleanUpFn should be defined with depth\");\n }\n if (useSignalsDepth === 1 && cleanUpFn) {\n try {\n cleanUpFn();\n } finally {\n inRender = false;\n cleanUpFn = undefined;\n }\n }\n } finally {\n useSignalsDepth--;\n }\n },\n [symDispose]() {\n this[EffectStoreFields.finishTracking]();\n },\n };\n}\n\n/**\n * @description this hook is for `@preact/signals-react-transform`. You should not use it until you know what you do. If s.f() is not called - reactivity will break\n * @example\n * ```tsx\n * const Component = () => {\n * const s = useSignals()\n * try {\n * // reading signals and using hooks here\n * const counter = useSignal(0)\n *\n * return (\n * <button onClick={() => counter.value++}>Click here: {counter.value * 2}</button>\n * )\n * } finally {\n * s.f()\n * }\n * }\n * ```\n * Custom hook to create the effect to track signals used during render and\n * subscribe to changes to rerender the component when the signals change.\n */\nexport function useSignals(): EffectStore {\n // console.log('useSignals')\n const storeRef = useRef<EffectStore>();\n if (storeRef.current == null) {\n storeRef.current = createEffectStore();\n }\n const store = storeRef.current;\n useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);\n\n store[EffectStoreFields.startTracking]();\n return store;\n}\n\n/**\n * A wrapper component that renders a Signal's value directly as a Text node or JSX.\n */\nfunction SignalValue(props: { data: Signal }) {\n const effectStore = useSignals();\n try {\n return props.data.value;\n } finally {\n effectStore[EffectStoreFields.finishTracking]();\n }\n}\n\n// Decorate Signals so React renders them as <SignalValue> components.\nObject.defineProperties(Signal.prototype, {\n $$typeof: { configurable: true, value: ReactElemType },\n type: { configurable: true, value: SignalValue },\n props: {\n configurable: true,\n get() {\n return { data: this };\n },\n },\n ref: { configurable: true, value: null },\n});\n"],"names":["version"],"mappings":"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"}