UNPKG

@preact-signals/safe-react

Version:
1 lines 11.4 kB
{"version":3,"file":"tracking.mjs","sources":["../../../src/lib/tracking.ts"],"sourcesContent":["\"use client\";\n\nimport { 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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"}