@preact/signals-react
Advanced tools
Comparing version 1.3.1 to 1.3.2
# @preact/signals-react | ||
## 1.3.2 | ||
### Patch Changes | ||
- [#358](https://github.com/preactjs/signals/pull/358) [`08ed3a0`](https://github.com/preactjs/signals/commit/08ed3a02a2291ad1e18389674d8ac20678064723) Thanks [@andrewiggins](https://github.com/andrewiggins)! - Add note to Readme about tradeoffs in current React integration | ||
* [#355](https://github.com/preactjs/signals/pull/355) [`21c8ee9`](https://github.com/preactjs/signals/commit/21c8ee98070a8bda05095dc91b64d2fe54042fb3) Thanks [@andrewiggins](https://github.com/andrewiggins)! - Fix React adapter in SSR and when rerendering | ||
- [#352](https://github.com/preactjs/signals/pull/352) [`a2b7320`](https://github.com/preactjs/signals/commit/a2b7320ee5829f58efaee5f7b20d993f35f09d2a) Thanks [@rschristian](https://github.com/rschristian)! - Uses full file path on useSyncExternalStore import, fixing a possible resolution issue in some build tools. | ||
## 1.3.1 | ||
@@ -4,0 +14,0 @@ |
@@ -1,1 +0,1 @@ | ||
var r=require("react"),n=require("react/jsx-runtime"),e=require("react/jsx-dev-runtime"),t=require("@preact/signals-core"),u=require("use-sync-external-store/shim/index");function i(r){return r&&"object"==typeof r&&"default"in r?r.default:r}var o,f=/*#__PURE__*/i(r),c=/*#__PURE__*/i(n),a=/*#__PURE__*/i(e),s=[],l=Symbol.for("react.element");function v(r){if(o)o();o=r&&r.S()}var p=!1,x=null;Object.defineProperty(r.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentDispatcher,"current",{get:function(){return x},set:function(r){if(!p){var n=y(x),e=y(r),i=n===b&&e===d;x=r;if(n===d&&e===b){p=!0;var o=function(r){var n=r.useRef();if(null==n.current)n.current=function(){var r,n,e=0,u=t.effect(function(){r=this});r.c=function(){e=e+1|0;if(n)n()};return{updater:r,subscribe:function(r){n=r;return function(){e=e+1|0;n=void 0;u()}},getSnapshot:function(){return e}}}();var e=n.current;u.useSyncExternalStore(e.subscribe,e.getSnapshot,e.getSnapshot);return e}(r);p=!1;v(o.updater)}else if(i)v()}else x=r}});var b=0,d=1,g=new Map;function y(r){if(!r)return d;var n,e=g.get(r);if(void 0!==e)return e;if(r.useCallback.length<2)n=d;else if(/Invalid/.test(r.useCallback))n=2;else n=b;g.set(r,n);return n}function h(r){if("function"!=typeof r)return r;else return function(n,e){if("string"==typeof n&&e)for(var u in e){var i=e[u];if("children"!==u&&i instanceof t.Signal)e[u]=i.value}return r.call.apply(r,[r,n,e].concat([].slice.call(arguments,2)))}}var j=c,m=a;f.createElement=h(f.createElement);m.jsx&&(m.jsx=h(m.jsx));j.jsx&&(j.jsx=h(j.jsx));m.jsxs&&(m.jsxs=h(m.jsxs));j.jsxs&&(j.jsxs=h(j.jsxs));m.jsxDEV&&(m.jsxDEV=h(m.jsxDEV));j.jsxDEV&&(j.jsxDEV=h(j.jsxDEV));Object.defineProperties(t.Signal.prototype,{$$typeof:{configurable:!0,value:l},type:{configurable:!0,value:function(r){return r.data.value}},props:{configurable:!0,get:function(){return{data:this}}},ref:{configurable:!0,value:null}});exports.Signal=t.Signal;exports.batch=t.batch;exports.computed=t.computed;exports.effect=t.effect;exports.signal=t.signal;exports.useComputed=function(n){var e=r.useRef(n);e.current=n;return r.useMemo(function(){return t.computed(function(){return e.current()})},s)};exports.useSignal=function(n){return r.useMemo(function(){return t.signal(n)},s)};exports.useSignalEffect=function(n){var e=r.useRef(n);e.current=n;r.useEffect(function(){return t.effect(function(){return e.current()})},s)};//# sourceMappingURL=signals.js.map | ||
var e=require("react"),r=require("react/jsx-runtime"),n=require("react/jsx-dev-runtime"),t=require("@preact/signals-core"),u=require("use-sync-external-store/shim/index.js");function i(e){return e&&"object"==typeof e&&"default"in e?e.default:e}var f,o=/*#__PURE__*/i(e),c=/*#__PURE__*/i(r),a=/*#__PURE__*/i(n),s=[],l=Symbol.for("react.element");function v(e){if(f)f();f=e&&e.S()}var p=!1,d=null;Object.defineProperty(e.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentDispatcher,"current",{get:function(){return d},set:function(e){if(!p){var r=b(d),n=b(e);d=e;if(function(e,r){if(1&e&&28&r)return!0;else if(2&e||2&r)return!1;else if(16&r)return!0;else return!1}(r,n)){p=!0;var i=function(e){var r=e.useRef();if(null==r.current)r.current=function(){var e,r,n=0,u=t.effect(function(){e=this});e.c=function(){n=n+1|0;if(r)r()};return{updater:e,subscribe:function(e){r=e;return function(){n=n+1|0;r=void 0;u()}},getSnapshot:function(){return n}}}();var n=r.current;u.useSyncExternalStore(n.subscribe,n.getSnapshot,n.getSnapshot);return n}(e);p=!1;v(i.updater)}else if(function(e,r){return Boolean(28&e&&1&r)}(r,n))v()}else d=e}});var x=new Map;function b(e){if(!e)return 1;var r,n=x.get(e);if(void 0!==n)return n;var t=e.useCallback.toString();if(e.useReducer===e.useEffect)r=1;else if(e.useEffect===e.useImperativeHandle)r=32;else if(/Invalid/.test(t))r=2;else if(/updateCallback/.test(t)||/\[0\]/.test(t)&&/\[1\]/.test(t)){var u=e.useReducer.toString();if(/rerenderReducer/.test(u)||/return\s*\[\w+,/.test(u))r=16;else r=8}else r=4;x.set(e,r);return r}function g(e){if("function"!=typeof e)return e;else return function(r,n){if("string"==typeof r&&n)for(var u in n){var i=n[u];if("children"!==u&&i instanceof t.Signal)n[u]=i.value}return e.call.apply(e,[e,r,n].concat([].slice.call(arguments,2)))}}var y=c,j=a;o.createElement=g(o.createElement);j.jsx&&(j.jsx=g(j.jsx));y.jsx&&(y.jsx=g(y.jsx));j.jsxs&&(j.jsxs=g(j.jsxs));y.jsxs&&(y.jsxs=g(y.jsxs));j.jsxDEV&&(j.jsxDEV=g(j.jsxDEV));y.jsxDEV&&(y.jsxDEV=g(y.jsxDEV));Object.defineProperties(t.Signal.prototype,{$$typeof:{configurable:!0,value:l},type:{configurable:!0,value:function(e){return e.data.value}},props:{configurable:!0,get:function(){return{data:this}}},ref:{configurable:!0,value:null}});exports.Signal=t.Signal;exports.batch=t.batch;exports.computed=t.computed;exports.effect=t.effect;exports.signal=t.signal;exports.useComputed=function(r){var n=e.useRef(r);n.current=r;return e.useMemo(function(){return t.computed(function(){return n.current()})},s)};exports.useSignal=function(r){return e.useMemo(function(){return t.signal(r)},s)};exports.useSignalEffect=function(r){var n=e.useRef(r);n.current=r;e.useEffect(function(){return t.effect(function(){return n.current()})},s)};//# sourceMappingURL=signals.js.map |
@@ -1,1 +0,1 @@ | ||
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports,require("react"),require("react/jsx-runtime"),require("react/jsx-dev-runtime"),require("@preact/signals-core"),require("use-sync-external-store/shim/index")):"function"==typeof define&&define.amd?define(["exports","react","react/jsx-runtime","react/jsx-dev-runtime","@preact/signals-core","use-sync-external-store/shim/index"],n):n((e||self).reactSignals={},e.react,e.jsxRuntime,e.jsxRuntimeDev,e.signalsCore,e.index)}(this,function(e,n,r,t,i,u){function f(e){return e&&"object"==typeof e&&"default"in e?e.default:e}var o,c=/*#__PURE__*/f(n),a=/*#__PURE__*/f(r),s=/*#__PURE__*/f(t),l=[],v=Symbol.for("react.element");function d(e){if(o)o();o=e&&e.S()}var p=!1,b=null;Object.defineProperty(n.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentDispatcher,"current",{get:function(){return b},set:function(e){if(!p){var n=h(b),r=h(e),t=n===g&&r===y;b=e;if(n===y&&r===g){p=!0;var f=function(e){var n=e.useRef();if(null==n.current)n.current=function(){var e,n,r=0,t=i.effect(function(){e=this});e.c=function(){r=r+1|0;if(n)n()};return{updater:e,subscribe:function(e){n=e;return function(){r=r+1|0;n=void 0;t()}},getSnapshot:function(){return r}}}();var r=n.current;u.useSyncExternalStore(r.subscribe,r.getSnapshot,r.getSnapshot);return r}(e);p=!1;d(f.updater)}else if(t)d()}else b=e}});var g=0,y=1,x=new Map;function h(e){if(!e)return y;var n,r=x.get(e);if(void 0!==r)return r;if(e.useCallback.length<2)n=y;else if(/Invalid/.test(e.useCallback))n=2;else n=g;x.set(e,n);return n}function m(e){if("function"!=typeof e)return e;else return function(n,r){if("string"==typeof n&&r)for(var t in r){var u=r[t];if("children"!==t&&u instanceof i.Signal)r[t]=u.value}return e.call.apply(e,[e,n,r].concat([].slice.call(arguments,2)))}}var j=a,q=s;c.createElement=m(c.createElement);q.jsx&&(q.jsx=m(q.jsx));j.jsx&&(j.jsx=m(j.jsx));q.jsxs&&(q.jsxs=m(q.jsxs));j.jsxs&&(j.jsxs=m(j.jsxs));q.jsxDEV&&(q.jsxDEV=m(q.jsxDEV));j.jsxDEV&&(j.jsxDEV=m(j.jsxDEV));Object.defineProperties(i.Signal.prototype,{$$typeof:{configurable:!0,value:v},type:{configurable:!0,value:function(e){return e.data.value}},props:{configurable:!0,get:function(){return{data:this}}},ref:{configurable:!0,value:null}});e.Signal=i.Signal;e.batch=i.batch;e.computed=i.computed;e.effect=i.effect;e.signal=i.signal;e.useComputed=function(e){var r=n.useRef(e);r.current=e;return n.useMemo(function(){return i.computed(function(){return r.current()})},l)};e.useSignal=function(e){return n.useMemo(function(){return i.signal(e)},l)};e.useSignalEffect=function(e){var r=n.useRef(e);r.current=e;n.useEffect(function(){return i.effect(function(){return r.current()})},l)}});//# sourceMappingURL=signals.min.js.map | ||
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports,require("react"),require("react/jsx-runtime"),require("react/jsx-dev-runtime"),require("@preact/signals-core"),require("use-sync-external-store/shim/index.js")):"function"==typeof define&&define.amd?define(["exports","react","react/jsx-runtime","react/jsx-dev-runtime","@preact/signals-core","use-sync-external-store/shim/index.js"],n):n((e||self).reactSignals={},e.react,e.jsxRuntime,e.jsxRuntimeDev,e.signalsCore,e.index_js)}(this,function(e,n,r,t,i,u){function f(e){return e&&"object"==typeof e&&"default"in e?e.default:e}var o,c=/*#__PURE__*/f(n),a=/*#__PURE__*/f(r),s=/*#__PURE__*/f(t),l=[],v=Symbol.for("react.element");function d(e){if(o)o();o=e&&e.S()}var p=!1,b=null;Object.defineProperty(n.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentDispatcher,"current",{get:function(){return b},set:function(e){if(!p){var n=y(b),r=y(e);b=e;if(function(e,n){if(1&e&&28&n)return!0;else if(2&e||2&n)return!1;else if(16&n)return!0;else return!1}(n,r)){p=!0;var t=function(e){var n=e.useRef();if(null==n.current)n.current=function(){var e,n,r=0,t=i.effect(function(){e=this});e.c=function(){r=r+1|0;if(n)n()};return{updater:e,subscribe:function(e){n=e;return function(){r=r+1|0;n=void 0;t()}},getSnapshot:function(){return r}}}();var r=n.current;u.useSyncExternalStore(r.subscribe,r.getSnapshot,r.getSnapshot);return r}(e);p=!1;d(t.updater)}else if(function(e,n){return Boolean(28&e&&1&n)}(n,r))d()}else b=e}});var g=new Map;function y(e){if(!e)return 1;var n,r=g.get(e);if(void 0!==r)return r;var t=e.useCallback.toString();if(e.useReducer===e.useEffect)n=1;else if(e.useEffect===e.useImperativeHandle)n=32;else if(/Invalid/.test(t))n=2;else if(/updateCallback/.test(t)||/\[0\]/.test(t)&&/\[1\]/.test(t)){var i=e.useReducer.toString();if(/rerenderReducer/.test(i)||/return\s*\[\w+,/.test(i))n=16;else n=8}else n=4;g.set(e,n);return n}function x(e){if("function"!=typeof e)return e;else return function(n,r){if("string"==typeof n&&r)for(var t in r){var u=r[t];if("children"!==t&&u instanceof i.Signal)r[t]=u.value}return e.call.apply(e,[e,n,r].concat([].slice.call(arguments,2)))}}var j=a,h=s;c.createElement=x(c.createElement);h.jsx&&(h.jsx=x(h.jsx));j.jsx&&(j.jsx=x(j.jsx));h.jsxs&&(h.jsxs=x(h.jsxs));j.jsxs&&(j.jsxs=x(j.jsxs));h.jsxDEV&&(h.jsxDEV=x(h.jsxDEV));j.jsxDEV&&(j.jsxDEV=x(j.jsxDEV));Object.defineProperties(i.Signal.prototype,{$$typeof:{configurable:!0,value:v},type:{configurable:!0,value:function(e){return e.data.value}},props:{configurable:!0,get:function(){return{data:this}}},ref:{configurable:!0,value:null}});e.Signal=i.Signal;e.batch=i.batch;e.computed=i.computed;e.effect=i.effect;e.signal=i.signal;e.useComputed=function(e){var r=n.useRef(e);r.current=e;return n.useMemo(function(){return i.computed(function(){return r.current()})},l)};e.useSignal=function(e){return n.useMemo(function(){return i.signal(e)},l)};e.useSignalEffect=function(e){var r=n.useRef(e);r.current=e;n.useEffect(function(){return i.effect(function(){return r.current()})},l)}});//# sourceMappingURL=signals.min.js.map |
@@ -1,1 +0,1 @@ | ||
import n,{__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED as r,useMemo as t,useRef as e,useEffect as i}from"react";import u from"react/jsx-runtime";import f from"react/jsx-dev-runtime";import{Signal as o,signal as c,computed as a,effect as l}from"@preact/signals-core";export{Signal,batch,computed,effect,signal}from"@preact/signals-core";import{useSyncExternalStore as s}from"use-sync-external-store/shim/index";var v,p=[],m=Symbol.for("react.element");function g(n){if(v)v();v=n&&n.S()}var b=!1,d=null;Object.defineProperty(r.ReactCurrentDispatcher,"current",{get:function(){return d},set:function(n){if(!b){var r=j(d),t=j(n),e=r===h&&t===x;d=n;if(r===x&&t===h){b=!0;var i=function(n){var r=n.useRef();if(null==r.current)r.current=function(){var n,r,t=0,e=l(function(){n=this});n.c=function(){t=t+1|0;if(r)r()};return{updater:n,subscribe:function(n){r=n;return function(){t=t+1|0;r=void 0;e()}},getSnapshot:function(){return t}}}();var t=r.current;s(t.subscribe,t.getSnapshot,t.getSnapshot);return t}(n);b=!1;g(i.updater)}else if(e)g()}else d=n}});var h=0,x=1,y=new Map;function j(n){if(!n)return x;var r,t=y.get(n);if(void 0!==t)return t;if(n.useCallback.length<2)r=x;else if(/Invalid/.test(n.useCallback))r=2;else r=h;y.set(n,r);return r}function S(n){if("function"!=typeof n)return n;else return function(r,t){if("string"==typeof r&&t)for(var e in t){var i=t[e];if("children"!==e&&i instanceof o)t[e]=i.value}return n.call.apply(n,[n,r,t].concat([].slice.call(arguments,2)))}}var O=u,$=f;n.createElement=S(n.createElement);$.jsx&&($.jsx=S($.jsx));O.jsx&&(O.jsx=S(O.jsx));$.jsxs&&($.jsxs=S($.jsxs));O.jsxs&&(O.jsxs=S(O.jsxs));$.jsxDEV&&($.jsxDEV=S($.jsxDEV));O.jsxDEV&&(O.jsxDEV=S(O.jsxDEV));Object.defineProperties(o.prototype,{$$typeof:{configurable:!0,value:m},type:{configurable:!0,value:function(n){return n.data.value}},props:{configurable:!0,get:function(){return{data:this}}},ref:{configurable:!0,value:null}});function useSignal(n){return t(function(){return c(n)},p)}function useComputed(n){var r=e(n);r.current=n;return t(function(){return a(function(){return r.current()})},p)}function useSignalEffect(n){var r=e(n);r.current=n;i(function(){return l(function(){return r.current()})},p)}export{useComputed,useSignal,useSignalEffect};//# sourceMappingURL=signals.module.js.map | ||
import r,{__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED as n,useMemo as e,useRef as t,useEffect as i}from"react";import u from"react/jsx-runtime";import f from"react/jsx-dev-runtime";import{Signal as o,signal as c,computed as a,effect as l}from"@preact/signals-core";export{Signal,batch,computed,effect,signal}from"@preact/signals-core";import{useSyncExternalStore as s}from"use-sync-external-store/shim/index.js";var v,p=[],m=Symbol.for("react.element");function d(r){if(v)v();v=r&&r.S()}var g=!1,b=null;Object.defineProperty(n.ReactCurrentDispatcher,"current",{get:function(){return b},set:function(r){if(!g){var n=x(b),e=x(r);b=r;if(function(r,n){if(1&r&&28&n)return!0;else if(2&r||2&n)return!1;else if(16&n)return!0;else return!1}(n,e)){g=!0;var t=function(r){var n=r.useRef();if(null==n.current)n.current=function(){var r,n,e=0,t=l(function(){r=this});r.c=function(){e=e+1|0;if(n)n()};return{updater:r,subscribe:function(r){n=r;return function(){e=e+1|0;n=void 0;t()}},getSnapshot:function(){return e}}}();var e=n.current;s(e.subscribe,e.getSnapshot,e.getSnapshot);return e}(r);g=!1;d(t.updater)}else if(function(r,n){return Boolean(28&r&&1&n)}(n,e))d()}else b=r}});var h=new Map;function x(r){if(!r)return 1;var n,e=h.get(r);if(void 0!==e)return e;var t=r.useCallback.toString();if(r.useReducer===r.useEffect)n=1;else if(r.useEffect===r.useImperativeHandle)n=32;else if(/Invalid/.test(t))n=2;else if(/updateCallback/.test(t)||/\[0\]/.test(t)&&/\[1\]/.test(t)){var i=r.useReducer.toString();if(/rerenderReducer/.test(i)||/return\s*\[\w+,/.test(i))n=16;else n=8}else n=4;h.set(r,n);return n}function y(r){if("function"!=typeof r)return r;else return function(n,e){if("string"==typeof n&&e)for(var t in e){var i=e[t];if("children"!==t&&i instanceof o)e[t]=i.value}return r.call.apply(r,[r,n,e].concat([].slice.call(arguments,2)))}}var j=u,S=f;r.createElement=y(r.createElement);S.jsx&&(S.jsx=y(S.jsx));j.jsx&&(j.jsx=y(j.jsx));S.jsxs&&(S.jsxs=y(S.jsxs));j.jsxs&&(j.jsxs=y(j.jsxs));S.jsxDEV&&(S.jsxDEV=y(S.jsxDEV));j.jsxDEV&&(j.jsxDEV=y(j.jsxDEV));Object.defineProperties(o.prototype,{$$typeof:{configurable:!0,value:m},type:{configurable:!0,value:function(r){return r.data.value}},props:{configurable:!0,get:function(){return{data:this}}},ref:{configurable:!0,value:null}});function useSignal(r){return e(function(){return c(r)},p)}function useComputed(r){var n=t(r);n.current=r;return e(function(){return a(function(){return n.current()})},p)}function useSignalEffect(r){var n=t(r);n.current=r;i(function(){return l(function(){return n.current()})},p)}export{useComputed,useSignal,useSignalEffect};//# sourceMappingURL=signals.module.js.map |
{ | ||
"name": "@preact/signals-react", | ||
"version": "1.3.1", | ||
"version": "1.3.2", | ||
"license": "MIT", | ||
@@ -5,0 +5,0 @@ "description": "Manage state with style in React", |
@@ -1,2 +0,1 @@ | ||
# Signals | ||
@@ -18,9 +17,9 @@ | ||
- [Guide / API](../../README.md#guide--api) | ||
- [`signal(initialValue)`](../../README.md#signalinitialvalue) | ||
- [`signal.peek()`](../../README.md#signalpeek) | ||
- [`computed(fn)`](../../README.md#computedfn) | ||
- [`effect(fn)`](../../README.md#effectfn) | ||
- [`batch(fn)`](../../README.md#batchfn) | ||
- [`signal(initialValue)`](../../README.md#signalinitialvalue) | ||
- [`signal.peek()`](../../README.md#signalpeek) | ||
- [`computed(fn)`](../../README.md#computedfn) | ||
- [`effect(fn)`](../../README.md#effectfn) | ||
- [`batch(fn)`](../../README.md#batchfn) | ||
- [React Integration](#react-integration) | ||
- [Hooks](#hooks) | ||
- [Hooks](#hooks) | ||
- [License](#license) | ||
@@ -30,2 +29,4 @@ | ||
> Note: The React integration plugs into some React internals and may break unexpectedly in future versions of React. If you are using Signals with React and encounter errors such as "Rendered more hooks than during previous render", "Should have a queue. This is likely a bug in React." or "Cannot redefine property: createElement" please open an issue here. | ||
The React integration can be installed via: | ||
@@ -73,2 +74,1 @@ | ||
`MIT`, see the [LICENSE](../../LICENSE) file. | ||
288
src/index.ts
@@ -9,4 +9,2 @@ import { | ||
type ReactElement, | ||
type useCallback, | ||
type useReducer, | ||
} from "react"; | ||
@@ -24,3 +22,3 @@ import React from "react"; | ||
} from "@preact/signals-core"; | ||
import { useSyncExternalStore } from "use-sync-external-store/shim/index"; | ||
import { useSyncExternalStore } from "use-sync-external-store/shim/index.js"; | ||
import type { Effect, JsxRuntimeModule } from "./internal"; | ||
@@ -34,6 +32,8 @@ | ||
interface ReactDispatcher { | ||
useRef: typeof useRef; | ||
useCallback: typeof useCallback; | ||
useReducer: typeof useReducer; | ||
useSyncExternalStore: typeof useSyncExternalStore; | ||
useRef: typeof React.useRef; | ||
useCallback: typeof React.useCallback; | ||
useReducer: typeof React.useReducer; | ||
useSyncExternalStore: typeof React.useSyncExternalStore; | ||
useEffect: typeof React.useEffect; | ||
useImperativeHandle: typeof React.useImperativeHandle; | ||
} | ||
@@ -125,2 +125,7 @@ | ||
// In order for signals to work in React, we need to observe what signals a | ||
// component uses while rendering. To do this, we need to know when a component | ||
// is rendering. To do this, we watch the transition of the | ||
// ReactCurrentDispatcher to know when a component is rerendering. | ||
// | ||
// To track when we are entering and exiting a component render (i.e. before and | ||
@@ -131,4 +136,5 @@ // after React renders a component), we track how the dispatcher changes. | ||
// from being used outside of components. Right before React renders a | ||
// component, the dispatcher is set to a valid one. Right after React finishes | ||
// rendering a component, the dispatcher is set to an erroring one again. This | ||
// component, the dispatcher is set to an instance that doesn't warn or error | ||
// and contains the implementations of all hooks. Right after React finishes | ||
// rendering a component, the dispatcher is set to the erroring one again. This | ||
// erroring dispatcher is called the `ContextOnlyDispatcher` in React's source. | ||
@@ -138,6 +144,6 @@ // | ||
// monitor the changes to the current ReactDispatcher. When the dispatcher | ||
// changes from the ContextOnlyDispatcher to a valid dispatcher, we assume we | ||
// changes from the ContextOnlyDispatcher to a "valid" dispatcher, we assume we | ||
// are entering a component render. At this point, we setup our | ||
// auto-subscriptions for any signals used in the component. We do this by | ||
// creating an effect and manually starting the effect. We use | ||
// creating an Signal effect and manually starting the Signal effect. We use | ||
// `useSyncExternalStore` to trigger rerenders on the component when any signals | ||
@@ -150,7 +156,13 @@ // it uses changes. | ||
// | ||
// Some edge cases to be aware of: | ||
// - In development, useReducer, useState, and useMemo changes the dispatcher to | ||
// a different erroring dispatcher before invoking the reducer and resets it | ||
// right after. | ||
// Some additional complexities to be aware of: | ||
// - If a component calls `setState` while rendering, React will re-render the | ||
// component immediately. Before triggering the re-render, React will change | ||
// the dispatcher to the HooksDispatcherOnRerender. When we transition to this | ||
// rerendering adapter, we need to re-trigger our hooks to keep the order of | ||
// hooks the same for every render of a component. | ||
// | ||
// - In development, useReducer, useState, and useMemo change the dispatcher to | ||
// a different warning dispatcher (not ContextOnlyDispatcher) before invoking | ||
// the reducer and resets it right after. | ||
// | ||
// The useSyncExternalStore shim will use some of these hooks when we invoke | ||
@@ -163,5 +175,5 @@ // it while entering a component render. We need to prevent this dispatcher | ||
// When a Component's function body invokes useReducer, useState, or useMemo, | ||
// this change in dispatcher should not signal that we are exiting a component | ||
// render. We ignore this change by detecting these dispatchers as different | ||
// from ContextOnlyDispatcher and other valid dispatchers. | ||
// this change in dispatcher should not signal that we are entering or exiting | ||
// a component render. We ignore this change by detecting these dispatchers as | ||
// different from ContextOnlyDispatcher and other valid dispatchers. | ||
// | ||
@@ -173,2 +185,76 @@ // - The `use` hook will change the dispatcher to from a valid update dispatcher | ||
// ContextOnlyDispatcher check, they do not affect our logic. | ||
// | ||
// - When server rendering, React does not change the dispatcher before and | ||
// after each component render. It sets it once for before the first render | ||
// and once for after the last render. This means that we will not be able to | ||
// detect when we are entering or exiting a component render. This is fine | ||
// because we don't need to detect this for server rendering. A component | ||
// can't trigger async rerenders in SSR so we don't need to track signals. | ||
// | ||
// If a component updates a signal value while rendering during SSR, we will | ||
// not rerender the component because the signal value will synchronously | ||
// change so all reads of the signal further down the tree will see the new | ||
// value. | ||
/* | ||
Below is a state machine definition for transitions between the various | ||
dispatchers in React's prod build. (It does not include dev time warning | ||
dispatchers which are just always ignored). | ||
ENTER and EXIT suffixes indicates whether this ReactCurrentDispatcher transition | ||
signals we are entering or exiting a component render, or if it doesn't signal a | ||
change in the component rendering lifecyle (NOOP). | ||
```js | ||
// Paste this into https://stately.ai/viz to visualize the state machine. | ||
import { createMachine } from "xstate"; | ||
// ENTER, EXIT, NOOP suffixes indicates whether this ReactCurrentDispatcher | ||
// transition signals we are entering or exiting a component render, or | ||
// if it doesn't signal a change in the component rendering lifecyle (NOOP). | ||
const dispatcherMachinePROD = createMachine({ | ||
id: "ReactCurrentDispatcher_PROD", | ||
initial: "null", | ||
states: { | ||
null: { | ||
on: { | ||
pushDispatcher: "ContextOnlyDispatcher", | ||
}, | ||
}, | ||
ContextOnlyDispatcher: { | ||
on: { | ||
renderWithHooks_Mount_ENTER: "HooksDispatcherOnMount", | ||
renderWithHooks_Update_ENTER: "HooksDispatcherOnUpdate", | ||
pushDispatcher_NOOP: "ContextOnlyDispatcher", | ||
popDispatcher_NOOP: "ContextOnlyDispatcher", | ||
}, | ||
}, | ||
HooksDispatcherOnMount: { | ||
on: { | ||
renderWithHooksAgain_ENTER: "HooksDispatcherOnRerender", | ||
resetHooksAfterThrow_EXIT: "ContextOnlyDispatcher", | ||
finishRenderingHooks_EXIT: "ContextOnlyDispatcher", | ||
}, | ||
}, | ||
HooksDispatcherOnUpdate: { | ||
on: { | ||
renderWithHooksAgain_ENTER: "HooksDispatcherOnRerender", | ||
resetHooksAfterThrow_EXIT: "ContextOnlyDispatcher", | ||
finishRenderingHooks_EXIT: "ContextOnlyDispatcher", | ||
use_ResumeSuspensedMount_NOOP: "HooksDispatcherOnMount", | ||
}, | ||
}, | ||
HooksDispatcherOnRerender: { | ||
on: { | ||
renderWithHooksAgain_ENTER: "HooksDispatcherOnRerender", | ||
resetHooksAfterThrow_EXIT: "ContextOnlyDispatcher", | ||
finishRenderingHooks_EXIT: "ContextOnlyDispatcher", | ||
}, | ||
}, | ||
}, | ||
}); | ||
``` | ||
*/ | ||
let lock = false; | ||
@@ -189,18 +275,6 @@ let currentDispatcher: ReactDispatcher | null = null; | ||
// We are entering a component render if the current dispatcher is the | ||
// ContextOnlyDispatcher and the next dispatcher is a valid dispatcher. | ||
const isEnteringComponentRender = | ||
currentDispatcherType === ContextOnlyDispatcherType && | ||
nextDispatcherType === ValidDispatcherType; | ||
// We are exiting a component render if the current dispatcher is a valid | ||
// dispatcher and the next dispatcher is the ContextOnlyDispatcher. | ||
const isExitingComponentRender = | ||
currentDispatcherType === ValidDispatcherType && | ||
nextDispatcherType === ContextOnlyDispatcherType; | ||
// Update the current dispatcher now so the hooks inside of the | ||
// useSyncExternalStore shim get the right dispatcher. | ||
currentDispatcher = nextDispatcher; | ||
if (isEnteringComponentRender) { | ||
if (isEnteringComponentRender(currentDispatcherType, nextDispatcherType)) { | ||
lock = true; | ||
@@ -211,3 +285,5 @@ const store = usePreactSignalStore(nextDispatcher); | ||
setCurrentUpdater(store.updater); | ||
} else if (isExitingComponentRender) { | ||
} else if ( | ||
isExitingComponentRender(currentDispatcherType, nextDispatcherType) | ||
) { | ||
setCurrentUpdater(); | ||
@@ -218,11 +294,14 @@ } | ||
const ValidDispatcherType = 0; | ||
const ContextOnlyDispatcherType = 1; | ||
const ErroringDispatcherType = 2; | ||
type DispatcherType = number; | ||
const ContextOnlyDispatcherType = 1 << 0; | ||
const WarningDispatcherType = 1 << 1; | ||
const MountDispatcherType = 1 << 2; | ||
const UpdateDispatcherType = 1 << 3; | ||
const RerenderDispatcherType = 1 << 4; | ||
const ServerDispatcherType = 1 << 5; | ||
const BrowserClientDispatcherType = | ||
MountDispatcherType | UpdateDispatcherType | RerenderDispatcherType; | ||
// We inject a useSyncExternalStore into every function component via | ||
// CurrentDispatcher. This prevents injecting into anything other than a | ||
// function component render. | ||
const dispatcherTypeCache = new Map<ReactDispatcher, number>(); | ||
function getDispatcherType(dispatcher: ReactDispatcher | null): number { | ||
const dispatcherTypeCache = new Map<ReactDispatcher, DispatcherType>(); | ||
function getDispatcherType(dispatcher: ReactDispatcher | null): DispatcherType { | ||
// Treat null the same as the ContextOnlyDispatcher. | ||
@@ -235,13 +314,51 @@ if (!dispatcher) return ContextOnlyDispatcherType; | ||
// The ContextOnlyDispatcher sets all the hook implementations to a function | ||
// that takes no arguments and throws and error. Check the number of arguments | ||
// for this dispatcher's useCallback implementation to determine if it is a | ||
// ContextOnlyDispatcher. All other dispatchers, erroring or not, define | ||
// functions with arguments and so fail this check. | ||
let type: number; | ||
if (dispatcher.useCallback.length < 2) { | ||
// that takes no arguments and throws and error. This dispatcher is the only | ||
// dispatcher where useReducer and useEffect will have the same | ||
// implementation. | ||
let type: DispatcherType; | ||
const useCallbackImpl = dispatcher.useCallback.toString(); | ||
if (dispatcher.useReducer === dispatcher.useEffect) { | ||
type = ContextOnlyDispatcherType; | ||
} else if (/Invalid/.test(dispatcher.useCallback as any)) { | ||
type = ErroringDispatcherType; | ||
// @ts-expect-error When server rendering, useEffect and useImperativeHandle | ||
// are both set to noop functions and so have the same implementation. | ||
} else if (dispatcher.useEffect === dispatcher.useImperativeHandle) { | ||
type = ServerDispatcherType; | ||
} else if (/Invalid/.test(useCallbackImpl)) { | ||
// We first check for warning dispatchers because they would also pass some | ||
// of the checks below. | ||
type = WarningDispatcherType; | ||
} else if ( | ||
// The development mount dispatcher invokes a function called | ||
// `mountCallback` whereas the development update/re-render dispatcher | ||
// invokes a function called `updateCallback`. Use that difference to | ||
// determine if we are in a mount or update-like dispatcher in development. | ||
// The production mount dispatcher defines an array of the form [callback, | ||
// deps] whereas update/re-render dispatchers read the array using array | ||
// indices (e.g. `[0]` and `[1]`). Use those differences to determine if we | ||
// are in a mount or update-like dispatcher in production. | ||
/updateCallback/.test(useCallbackImpl) || | ||
(/\[0\]/.test(useCallbackImpl) && /\[1\]/.test(useCallbackImpl)) | ||
) { | ||
// The update and rerender dispatchers have different implementations for | ||
// useReducer. We'll check it's implementation to determine if this is the | ||
// rerender or update dispatcher. | ||
let useReducerImpl = dispatcher.useReducer.toString(); | ||
if ( | ||
// The development rerender dispatcher invokes a function called | ||
// `rerenderReducer` whereas the update dispatcher invokes a function | ||
// called `updateReducer`. The production rerender dispatcher returns an | ||
// array of the form `[state, dispatch]` whereas the update dispatcher | ||
// returns an array of `[fiber.memoizedState, dispatch]` so we check the | ||
// return statement in the implementation of useReducer to differentiate | ||
// between the two. | ||
/rerenderReducer/.test(useReducerImpl) || | ||
/return\s*\[\w+,/.test(useReducerImpl) | ||
) { | ||
type = RerenderDispatcherType; | ||
} else { | ||
type = UpdateDispatcherType; | ||
} | ||
} else { | ||
type = ValidDispatcherType; | ||
type = MountDispatcherType; | ||
} | ||
@@ -253,2 +370,75 @@ | ||
function isEnteringComponentRender( | ||
currentDispatcherType: DispatcherType, | ||
nextDispatcherType: DispatcherType | ||
): boolean { | ||
if ( | ||
currentDispatcherType & ContextOnlyDispatcherType && | ||
nextDispatcherType & BrowserClientDispatcherType | ||
) { | ||
// ## Mount or update (ContextOnlyDispatcher -> ValidDispatcher (Mount or Update)) | ||
// | ||
// If the current dispatcher is the ContextOnlyDispatcher and the next | ||
// dispatcher is a valid dispatcher, we are entering a component render. | ||
return true; | ||
} else if ( | ||
currentDispatcherType & WarningDispatcherType || | ||
nextDispatcherType & WarningDispatcherType | ||
) { | ||
// ## Warning dispatcher | ||
// | ||
// If the current dispatcher or next dispatcher is an warning dispatcher, | ||
// we are not entering a component render. The current warning dispatchers | ||
// are used to warn when hooks are nested improperly and do not indicate | ||
// entering a new component render. | ||
return false; | ||
} else if (nextDispatcherType & RerenderDispatcherType) { | ||
// Any transition into the rerender dispatcher is the beginning of a | ||
// component render, so we should invoke our hooks. Details below. | ||
// | ||
// ## In-place rerendering (e.g. Mount -> Rerender) | ||
// | ||
// If we are transitioning from the mount, update, or rerender dispatcher to | ||
// the rerender dispatcher (e.g. HooksDispatcherOnMount to | ||
// HooksDispatcherOnRerender), then this component is rerendering due to | ||
// calling setState inside of its function body. We are re-entering a | ||
// component's render method and so we should re-invoke our hooks. | ||
return true; | ||
} else { | ||
// ## Resuming suspended mount edge case (Update -> Mount) | ||
// | ||
// If we are transitioning from the update dispatcher to the mount | ||
// dispatcher, then this component is using the `use` hook and is resuming | ||
// from a mount. We should not re-invoke our hooks in this situation since | ||
// we are not entering a new component render, but instead continuing a | ||
// previous render. | ||
// | ||
// ## Other transitions | ||
// | ||
// For example, Mount -> Mount, Update -> Update, Mount -> Update, any | ||
// transition in and out of invalid dispatchers. | ||
// | ||
// There is no known transition for the following transitions so we default | ||
// to not triggering a re-enter of the component. | ||
// - HooksDispatcherOnMount -> HooksDispatcherOnMount | ||
// - HooksDispatcherOnMount -> HooksDispatcherOnUpdate | ||
// - HooksDispatcherOnUpdate -> HooksDispatcherOnUpdate | ||
return false; | ||
} | ||
} | ||
/** | ||
* We are exiting a component render if the current dispatcher is a valid | ||
* dispatcher and the next dispatcher is the ContextOnlyDispatcher. | ||
*/ | ||
function isExitingComponentRender( | ||
currentDispatcherType: DispatcherType, | ||
nextDispatcherType: DispatcherType | ||
): boolean { | ||
return Boolean( | ||
currentDispatcherType & BrowserClientDispatcherType && | ||
nextDispatcherType & ContextOnlyDispatcherType | ||
); | ||
} | ||
function WrapJsx<T>(jsx: T): T { | ||
@@ -255,0 +445,0 @@ if (typeof jsx !== "function") return jsx; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
171958
23
1442
1
1