From 2d5f02ffd1a4424bc81e27ba562eaf90f32c799c Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Mon, 22 Jan 2024 15:15:41 +0100 Subject: [PATCH 01/21] enable `react-hooks` lint rules --- .eslintrc | 1 + package-lock.json | 349 ++++++++++++++---- package.json | 1 + patches/eslint-plugin-react-hooks+4.6.0.patch | 28 ++ src/react/hooks/useLazyQuery.ts | 16 +- src/react/hooks/useLoadableQuery.ts | 11 +- src/react/hooks/useMutation.ts | 50 ++- src/react/hooks/useQuery.ts | 15 +- src/react/hooks/useReadQuery.ts | 2 +- src/react/hooks/useSubscription.ts | 8 +- src/react/hooks/useSuspenseQuery.ts | 4 + src/react/hooks/useSyncExternalStore.ts | 4 + 12 files changed, 380 insertions(+), 109 deletions(-) create mode 100644 patches/eslint-plugin-react-hooks+4.6.0.patch diff --git a/.eslintrc b/.eslintrc index dccf775414b..a15c961ab3b 100644 --- a/.eslintrc +++ b/.eslintrc @@ -23,6 +23,7 @@ { "files": ["**/*.ts", "**/*.tsx"], "excludedFiles": ["**/__tests__/**/*.*", "*.d.ts"], + "extends": ["plugin:react-hooks/recommended"], "parserOptions": { "project": "./tsconfig.json" }, diff --git a/package-lock.json b/package-lock.json index 25229d1d759..738a53c6014 100644 --- a/package-lock.json +++ b/package-lock.json @@ -65,6 +65,7 @@ "eslint-import-resolver-typescript": "3.6.1", "eslint-plugin-import": "npm:@phryneas/eslint-plugin-import@2.27.5-pr.2813.2817.199971c", "eslint-plugin-local-rules": "2.0.1", + "eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-testing-library": "6.2.0", "expect-type": "0.17.3", "fetch-mock": "9.11.0", @@ -4027,6 +4028,19 @@ "node": ">=6.0" } }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array-includes": { "version": "3.1.6", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", @@ -4110,6 +4124,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", + "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "is-array-buffer": "^3.0.2", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", @@ -5147,11 +5182,12 @@ } }, "node_modules/define-properties": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", - "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dev": true, "dependencies": { + "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" }, @@ -5359,35 +5395,50 @@ } }, "node_modules/es-abstract": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.4.tgz", - "integrity": "sha512-0UtvRN79eMe2L+UNEF1BwRe364sj/DXhQ/k5FmivgoSdpM90b8Jc0mDzKMGo7QS0BVbOP/bTwBKNnDc9rNzaPA==", + "version": "1.22.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", + "integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", + "array-buffer-byte-length": "^1.0.0", + "arraybuffer.prototype.slice": "^1.0.2", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.5", + "es-set-tostringtag": "^2.0.1", "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.1.3", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.2", "get-symbol-description": "^1.0.0", - "has": "^1.0.3", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", + "hasown": "^2.0.0", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", "is-callable": "^1.2.7", "is-negative-zero": "^2.0.2", "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.2", "is-string": "^1.0.7", + "is-typed-array": "^1.1.12", "is-weakref": "^1.0.2", - "object-inspect": "^1.12.2", + "object-inspect": "^1.13.1", "object-keys": "^1.1.1", "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", + "regexp.prototype.flags": "^1.5.1", + "safe-array-concat": "^1.0.1", "safe-regex-test": "^1.0.0", - "string.prototype.trimend": "^1.0.5", - "string.prototype.trimstart": "^1.0.5", - "unbox-primitive": "^1.0.2" + "string.prototype.trim": "^1.2.8", + "string.prototype.trimend": "^1.0.7", + "string.prototype.trimstart": "^1.0.7", + "typed-array-buffer": "^1.0.0", + "typed-array-byte-length": "^1.0.0", + "typed-array-byte-offset": "^1.0.0", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -5396,6 +5447,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-set-tostringtag": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", + "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.2", + "has-tostringtag": "^1.0.0", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-shim-unscopables": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", @@ -5734,6 +5799,18 @@ "integrity": "sha512-AJhGd+GcI5r2dbjiGPixM8jnBl0XFxqoVbqzwKbYz+nTk+Cj5dNE3+OlhC176bl5r25KsGsIthLi1VqIW5Ga+A==", "dev": true }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, "node_modules/eslint-plugin-testing-library": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-6.2.0.tgz", @@ -6565,15 +6642,15 @@ } }, "node_modules/function.prototype.name": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", - "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" }, "engines": { "node": ">= 0.4" @@ -6740,6 +6817,21 @@ "node": ">=4" } }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -7110,13 +7202,13 @@ "dev": true }, "node_modules/internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", + "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", "dev": true, "dependencies": { - "get-intrinsic": "^1.1.0", - "has": "^1.0.3", + "get-intrinsic": "^1.2.2", + "hasown": "^2.0.0", "side-channel": "^1.0.4" }, "engines": { @@ -7139,6 +7231,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -7497,16 +7603,12 @@ } }, "node_modules/is-typed-array": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", + "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", "dev": true, "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" + "which-typed-array": "^1.1.11" }, "engines": { "node": ">= 0.4" @@ -9547,9 +9649,9 @@ } }, "node_modules/object-inspect": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -10229,13 +10331,13 @@ } }, "node_modules/prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", - "react-is": "^16.8.1" + "react-is": "^16.13.1" } }, "node_modules/pseudomap": { @@ -10406,9 +10508,9 @@ } }, "node_modules/react-is": { - "version": "16.13.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.0.tgz", - "integrity": "sha512-GFMtL0vHkiBv9HluwNZTggSn/sCyEt9n02aM0dSAjGGyqyNlAyftYm4phPxdvCigG15JreC5biwxCgTAJZ7yAA==" + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/read-pkg": { "version": "5.2.0", @@ -10541,14 +10643,14 @@ "dev": true }, "node_modules/regexp.prototype.flags": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", - "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", + "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "functions-have-names": "^1.2.2" + "define-properties": "^1.2.0", + "set-function-name": "^2.0.0" }, "engines": { "node": ">= 0.4" @@ -10852,6 +10954,24 @@ "tslib": "^2.1.0" } }, + "node_modules/safe-array-concat": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.0.tgz", + "integrity": "sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "get-intrinsic": "^1.2.2", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -10938,6 +11058,20 @@ "node": ">= 0.4" } }, + "node_modules/set-function-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", + "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -11425,29 +11559,46 @@ "node": ">=8" } }, + "node_modules/string.prototype.trim": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", + "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/string.prototype.trimend": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", - "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", + "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/string.prototype.trimstart": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", - "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", + "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -12036,6 +12187,71 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typed-array-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", + "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", + "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", + "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/typedoc": { "version": "0.25.0", "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.0.tgz", @@ -12456,17 +12672,16 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", - "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", + "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", "dev": true, "dependencies": { "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", + "call-bind": "^1.0.4", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" + "has-tostringtag": "^1.0.0" }, "engines": { "node": ">= 0.4" diff --git a/package.json b/package.json index 2859fe83766..20371ffbc65 100644 --- a/package.json +++ b/package.json @@ -146,6 +146,7 @@ "eslint-import-resolver-typescript": "3.6.1", "eslint-plugin-import": "npm:@phryneas/eslint-plugin-import@2.27.5-pr.2813.2817.199971c", "eslint-plugin-local-rules": "2.0.1", + "eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-testing-library": "6.2.0", "expect-type": "0.17.3", "fetch-mock": "9.11.0", diff --git a/patches/eslint-plugin-react-hooks+4.6.0.patch b/patches/eslint-plugin-react-hooks+4.6.0.patch new file mode 100644 index 00000000000..a8356f0f3df --- /dev/null +++ b/patches/eslint-plugin-react-hooks+4.6.0.patch @@ -0,0 +1,28 @@ +diff --git a/node_modules/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.development.js b/node_modules/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.development.js +index 441442f..d1ec5dc 100644 +--- a/node_modules/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.development.js ++++ b/node_modules/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.development.js +@@ -905,7 +905,7 @@ var ExhaustiveDeps = { + var _callee = callee, + name = _callee.name; + +- if (name === 'useRef' && id.type === 'Identifier') { ++ if ((name === 'useRef' || name === "useLazyRef") && id.type === 'Identifier') { + // useRef() return value is stable. + return true; + } else if (name === 'useState' || name === 'useReducer') { +diff --git a/node_modules/eslint-plugin-react-hooks/index.js b/node_modules/eslint-plugin-react-hooks/index.js +index 0e91baf..7e86d46 100644 +--- a/node_modules/eslint-plugin-react-hooks/index.js ++++ b/node_modules/eslint-plugin-react-hooks/index.js +@@ -1,9 +1,3 @@ + 'use strict'; + +-// TODO: this doesn't make sense for an ESLint rule. +-// We need to fix our build process to not create bundles for "raw" packages like this. +-if (process.env.NODE_ENV === 'production') { +- module.exports = require('./cjs/eslint-plugin-react-hooks.production.min.js'); +-} else { +- module.exports = require('./cjs/eslint-plugin-react-hooks.development.js'); +-} ++module.exports = require('./cjs/eslint-plugin-react-hooks.development.js'); diff --git a/src/react/hooks/useLazyQuery.ts b/src/react/hooks/useLazyQuery.ts index e0f237dffbc..71d83e82a59 100644 --- a/src/react/hooks/useLazyQuery.ts +++ b/src/react/hooks/useLazyQuery.ts @@ -9,7 +9,6 @@ import type { LazyQueryHookOptions, LazyQueryResultTuple, NoInfer, - QueryResult, } from "../types/types.js"; import { useInternalState } from "./useQuery.js"; import { useApolloClient } from "./useApolloClient.js"; @@ -60,15 +59,13 @@ export function useLazyQuery< useQueryResult.observable.options.initialFetchPolicy || internalState.getDefaultFetchPolicy(); - const result: QueryResult = Object.assign(useQueryResult, { - called: !!execOptionsRef.current, - }); + useQueryResult.called = !!execOptionsRef.current; // We use useMemo here to make sure the eager methods have a stable identity. const eagerMethods = React.useMemo(() => { const eagerMethods: Record = {}; for (const key of EAGER_METHODS) { - const method = result[key]; + const method = useQueryResult[key]; eagerMethods[key] = function () { if (!execOptionsRef.current) { execOptionsRef.current = Object.create(null); @@ -81,9 +78,12 @@ export function useLazyQuery< } return eagerMethods; + // React Hook React.useMemo has missing dependencies: 'internalState' and 'useQueryResult'. + // Both `internalState` as well as all of the handlers are stable references. + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - Object.assign(result, eagerMethods); + Object.assign(useQueryResult, eagerMethods); const execute = React.useCallback[0]>( (executeOptions) => { @@ -112,8 +112,8 @@ export function useLazyQuery< return promise; }, - [] + [eagerMethods, initialFetchPolicy, internalState] ); - return [execute, result]; + return [execute, useQueryResult]; } diff --git a/src/react/hooks/useLoadableQuery.ts b/src/react/hooks/useLoadableQuery.ts index 796721a92fc..f6a8d8a2a57 100644 --- a/src/react/hooks/useLoadableQuery.ts +++ b/src/react/hooks/useLoadableQuery.ts @@ -192,12 +192,19 @@ export function useLoadableQuery< setQueryRef(wrapQueryRef(queryRef)); }, - [query, queryKey, suspenseCache, watchQueryOptions, calledDuringRender] + [ + query, + queryKey, + suspenseCache, + watchQueryOptions, + calledDuringRender, + client, + ] ); const reset: ResetFunction = React.useCallback(() => { setQueryRef(null); - }, [queryRef]); + }, []); return [loadQuery, queryRef, { fetchMore, refetch, reset }]; } diff --git a/src/react/hooks/useMutation.ts b/src/react/hooks/useMutation.ts index ac3d3cfea8f..c7c9f8c9bb9 100644 --- a/src/react/hooks/useMutation.ts +++ b/src/react/hooks/useMutation.ts @@ -43,7 +43,7 @@ export function useMutation< client, }); - const ref = React.useRef({ + const { current } = React.useRef({ result, mutationId: 0, isMounted: true, @@ -55,7 +55,7 @@ export function useMutation< // TODO: Trying to assign these in a useEffect or useLayoutEffect breaks // higher-order components. { - Object.assign(ref.current, { client, options, mutation }); + Object.assign(current, { client, options, mutation }); } const execute = React.useCallback( @@ -67,17 +67,17 @@ export function useMutation< TCache > = {} ) => { - const { options, mutation } = ref.current; + const { options, mutation } = current; const baseOptions = { ...options, mutation }; - const client = executeOptions.client || ref.current.client; + const client = executeOptions.client || current.client; if ( - !ref.current.result.loading && + !current.result.loading && !baseOptions.ignoreResults && - ref.current.isMounted + current.isMounted ) { setResult( - (ref.current.result = { + (current.result = { loading: true, error: void 0, data: void 0, @@ -87,7 +87,7 @@ export function useMutation< ); } - const mutationId = ++ref.current.mutationId; + const mutationId = ++current.mutationId; const clientOptions = mergeOptions(baseOptions, executeOptions); return client @@ -99,8 +99,7 @@ export function useMutation< new ApolloError({ graphQLErrors: errors }) : void 0; - const onError = - executeOptions.onError || ref.current.options?.onError; + const onError = executeOptions.onError || current.options?.onError; if (error && onError) { onError( @@ -110,7 +109,7 @@ export function useMutation< } if ( - mutationId === ref.current.mutationId && + mutationId === current.mutationId && !clientOptions.ignoreResults ) { const result = { @@ -121,13 +120,13 @@ export function useMutation< client, }; - if (ref.current.isMounted && !equal(ref.current.result, result)) { - setResult((ref.current.result = result)); + if (current.isMounted && !equal(current.result, result)) { + setResult((current.result = result)); } } const onCompleted = - executeOptions.onCompleted || ref.current.options?.onCompleted; + executeOptions.onCompleted || current.options?.onCompleted; if (!error) { onCompleted?.( @@ -139,7 +138,7 @@ export function useMutation< return response; }) .catch((error) => { - if (mutationId === ref.current.mutationId && ref.current.isMounted) { + if (mutationId === current.mutationId && current.isMounted) { const result = { loading: false, error, @@ -148,13 +147,12 @@ export function useMutation< client, }; - if (!equal(ref.current.result, result)) { - setResult((ref.current.result = result)); + if (!equal(current.result, result)) { + setResult((current.result = result)); } } - const onError = - executeOptions.onError || ref.current.options?.onError; + const onError = executeOptions.onError || current.options?.onError; if (onError) { onError( @@ -169,24 +167,24 @@ export function useMutation< throw error; }); }, - [] + [current] ); const reset = React.useCallback(() => { - if (ref.current.isMounted) { + if (current.isMounted) { const result = { called: false, loading: false, client }; - Object.assign(ref.current, { mutationId: 0, result }); + Object.assign(current, { mutationId: 0, result }); setResult(result); } - }, []); + }, [client, current]); React.useEffect(() => { - ref.current.isMounted = true; + current.isMounted = true; return () => { - ref.current.isMounted = false; + current.isMounted = false; }; - }, []); + }, [current]); return [execute, { reset, ...result }]; } diff --git a/src/react/hooks/useQuery.ts b/src/react/hooks/useQuery.ts index b83f8558888..ca42f8be33e 100644 --- a/src/react/hooks/useQuery.ts +++ b/src/react/hooks/useQuery.ts @@ -174,16 +174,24 @@ class InternalState { // initialization, this.renderPromises is usually undefined (unless SSR is // happening), but that's fine as long as it has been initialized that way, // rather than left uninitialized. + + // React Hook "React.useContext" cannot be called in a class component. + // eslint-disable-next-line react-hooks/rules-of-hooks this.renderPromises = React.useContext(getApolloContext()).renderPromises; this.useOptions(options); const obsQuery = this.useObservableQuery(); + const renderPromises = this.renderPromises; + // React Hook "useSyncExternalStore" cannot be called in a class component. + // eslint-disable-next-line react-hooks/rules-of-hooks const result = useSyncExternalStore( + // React Hook "React.useCallback" cannot be called in a class component. + // eslint-disable-next-line react-hooks/rules-of-hooks React.useCallback( (handleStoreChange) => { - if (this.renderPromises) { + if (renderPromises) { return () => {}; } @@ -251,8 +259,7 @@ class InternalState { // effectively passing this dependency array to that useEffect buried // inside useSyncExternalStore, as desired. obsQuery, - this.renderPromises, - this.client.disableNetworkFetches, + renderPromises, ] ), @@ -477,6 +484,8 @@ class InternalState { this.observable || // Reuse this.observable if possible (and not SSR) this.client.watchQuery(this.getObsQueryOptions())); + // React Hook "React.useMemo" cannot be called in a class component. + // eslint-disable-next-line react-hooks/rules-of-hooks this.obsQueryFields = React.useMemo( () => ({ refetch: obsQuery.refetch.bind(obsQuery), diff --git a/src/react/hooks/useReadQuery.ts b/src/react/hooks/useReadQuery.ts index 3f110b26164..f7e7a0c260d 100644 --- a/src/react/hooks/useReadQuery.ts +++ b/src/react/hooks/useReadQuery.ts @@ -64,7 +64,7 @@ export function useReadQuery( forceUpdate(); }); }, - [internalQueryRef] + [internalQueryRef, queryRef] ), getPromise, getPromise diff --git a/src/react/hooks/useSubscription.ts b/src/react/hooks/useSubscription.ts index 33029adbb9a..72f782d9250 100644 --- a/src/react/hooks/useSubscription.ts +++ b/src/react/hooks/useSubscription.ts @@ -120,7 +120,7 @@ export function useSubscription< } Object.assign(ref.current, { client, subscription, options }); - }, [client, subscription, options, canResetObservableRef.current]); + }, [client, subscription, options]); React.useEffect(() => { if (!observable) { @@ -187,7 +187,11 @@ export function useSubscription< subscription.unsubscribe(); }); }; - }, [observable]); + // React Hook React.useEffect has a missing dependency: 'options?.variables'. + // This seems like a valid complaint and we should investigate that. + // Generally: why is this a second `useEffect` and not part of the first one? + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [client, observable]); return result; } diff --git a/src/react/hooks/useSuspenseQuery.ts b/src/react/hooks/useSuspenseQuery.ts index 3a69e175ed5..41969d2e0ae 100644 --- a/src/react/hooks/useSuspenseQuery.ts +++ b/src/react/hooks/useSuspenseQuery.ts @@ -235,6 +235,10 @@ export function useSuspenseQuery< }, [queryRef.result]); const result = fetchPolicy === "standby" ? skipResult : __use(promise); + // eslint incorrectly reports + // React Hook useCallback received a function whose dependencies are unknown. Pass an inline function instead. + // seems to be a parsing error because of the `satisfies as` + // eslint-disable-next-line react-hooks/exhaustive-deps const fetchMore = React.useCallback( ((options) => { const promise = queryRef.fetchMore(options); diff --git a/src/react/hooks/useSyncExternalStore.ts b/src/react/hooks/useSyncExternalStore.ts index adf4d059f7f..1cecbe90eac 100644 --- a/src/react/hooks/useSyncExternalStore.ts +++ b/src/react/hooks/useSyncExternalStore.ts @@ -81,6 +81,8 @@ export const useSyncExternalStore: RealUseSESHookType = // Force a re-render. forceUpdate({ inst }); } + // React Hook React.useLayoutEffect has a missing dependency: 'inst'. Either include it or remove the dependency array. + // eslint-disable-next-line react-hooks/exhaustive-deps }, [subscribe, value, getSnapshot]); } else { Object.assign(inst, { value, getSnapshot }); @@ -108,6 +110,8 @@ export const useSyncExternalStore: RealUseSESHookType = forceUpdate({ inst }); } }); + // React Hook React.useEffect has a missing dependency: 'inst'. Either include it or remove the dependency array. + // eslint-disable-next-line react-hooks/exhaustive-deps }, [subscribe]); return value; From 9e786aa8655d07e277e2c7b9db699fece801d2c2 Mon Sep 17 00:00:00 2001 From: phryneas Date: Thu, 16 May 2024 14:35:36 +0000 Subject: [PATCH 02/21] Clean up Prettier, Size-limit, and Api-Extractor --- .size-limits.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.size-limits.json b/.size-limits.json index baaaf675f5a..39967e70332 100644 --- a/.size-limits.json +++ b/.size-limits.json @@ -1,4 +1,4 @@ { - "dist/apollo-client.min.cjs": 39573, + "dist/apollo-client.min.cjs": 39565, "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32821 } From d75778122390a3af6a12abdf063df7e43427509a Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Thu, 16 May 2024 16:54:56 +0200 Subject: [PATCH 03/21] update eslint rule --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 942020aabbe..3cc6d6748b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -67,7 +67,7 @@ "eslint-import-resolver-typescript": "3.6.1", "eslint-plugin-import": "npm:@phryneas/eslint-plugin-import@2.27.5-pr.2813.2817.199971c", "eslint-plugin-local-rules": "2.0.1", - "eslint-plugin-react-hooks": "4.6.0", + "eslint-plugin-react-hooks": "4.6.2", "eslint-plugin-testing-library": "6.2.2", "expect-type": "0.19.0", "fetch-mock": "9.11.0", @@ -5864,9 +5864,9 @@ "dev": true }, "node_modules/eslint-plugin-react-hooks": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", - "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", + "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", "dev": true, "engines": { "node": ">=10" diff --git a/package.json b/package.json index 1c9c5c46399..4f510aebe17 100644 --- a/package.json +++ b/package.json @@ -148,7 +148,7 @@ "eslint-import-resolver-typescript": "3.6.1", "eslint-plugin-import": "npm:@phryneas/eslint-plugin-import@2.27.5-pr.2813.2817.199971c", "eslint-plugin-local-rules": "2.0.1", - "eslint-plugin-react-hooks": "4.6.0", + "eslint-plugin-react-hooks": "4.6.2", "eslint-plugin-testing-library": "6.2.2", "expect-type": "0.19.0", "fetch-mock": "9.11.0", From 0e5e3cb5681d2cf7d0dc2076cf56db6923f45eb0 Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Thu, 16 May 2024 16:55:17 +0200 Subject: [PATCH 04/21] different type casting to not break lint rule parsing --- src/react/hooks/useSuspenseQuery.ts | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/react/hooks/useSuspenseQuery.ts b/src/react/hooks/useSuspenseQuery.ts index ad158053465..fe438ab6240 100644 --- a/src/react/hooks/useSuspenseQuery.ts +++ b/src/react/hooks/useSuspenseQuery.ts @@ -251,22 +251,18 @@ function _useSuspenseQuery< }, [queryRef.result]); const result = fetchPolicy === "standby" ? skipResult : __use(promise); - // eslint incorrectly reports - // React Hook useCallback received a function whose dependencies are unknown. Pass an inline function instead. - // seems to be a parsing error because of the `satisfies as` - // eslint-disable-next-line react-hooks/exhaustive-deps - const fetchMore = React.useCallback( - ((options) => { + + const fetchMore = React.useCallback< + FetchMoreFunction + >( + (options) => { const promise = queryRef.fetchMore(options); setPromise([queryRef.key, queryRef.promise]); return promise; - }) satisfies FetchMoreFunction< - unknown, - OperationVariables - > as FetchMoreFunction, + }, [queryRef] - ); + ) as FetchMoreFunction; const refetch: RefetchFunction = React.useCallback( (variables) => { From 6ad1b0ea068d91d6d01606d99f05bbfea24ef78f Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Thu, 16 May 2024 17:20:31 +0200 Subject: [PATCH 05/21] fix up useMutation + lifecycle test --- .../__tests__/mutations/lifecycle.test.tsx | 4 +- src/react/hooks/useMutation.ts | 59 ++++++++++--------- 2 files changed, 35 insertions(+), 28 deletions(-) diff --git a/src/react/hoc/__tests__/mutations/lifecycle.test.tsx b/src/react/hoc/__tests__/mutations/lifecycle.test.tsx index f74895780a3..f6ff711e48d 100644 --- a/src/react/hoc/__tests__/mutations/lifecycle.test.tsx +++ b/src/react/hoc/__tests__/mutations/lifecycle.test.tsx @@ -92,7 +92,9 @@ describe("graphql(mutation) lifecycle", () => { class Container extends React.Component> { render() { if (this.props.listId !== 2) return null; - this.props.mutate!().then(() => resolve()); + setTimeout(() => { + this.props.mutate!().then(() => resolve()); + }); return null; } } diff --git a/src/react/hooks/useMutation.ts b/src/react/hooks/useMutation.ts index d54ab7abe37..65f0820e015 100644 --- a/src/react/hooks/useMutation.ts +++ b/src/react/hooks/useMutation.ts @@ -90,7 +90,7 @@ export function useMutation< client, }); - const { current } = React.useRef({ + const ref = React.useRef({ result, mutationId: 0, isMounted: true, @@ -99,11 +99,9 @@ export function useMutation< options, }); - // TODO: Trying to assign these in a useEffect or useLayoutEffect breaks - // higher-order components. - { - Object.assign(current, { client, options, mutation }); - } + React.useLayoutEffect(() => { + Object.assign(ref.current, { client, options, mutation }); + }); const execute = React.useCallback( ( @@ -114,17 +112,17 @@ export function useMutation< TCache > = {} ) => { - const { options, mutation } = current; + const { options, mutation } = ref.current; const baseOptions = { ...options, mutation }; - const client = executeOptions.client || current.client; + const client = executeOptions.client || ref.current.client; if ( - !current.result.loading && + !ref.current.result.loading && !baseOptions.ignoreResults && - current.isMounted + ref.current.isMounted ) { setResult( - (current.result = { + (ref.current.result = { loading: true, error: void 0, data: void 0, @@ -134,7 +132,7 @@ export function useMutation< ); } - const mutationId = ++current.mutationId; + const mutationId = ++ref.current.mutationId; const clientOptions = mergeOptions(baseOptions, executeOptions); return client @@ -146,7 +144,8 @@ export function useMutation< new ApolloError({ graphQLErrors: errors }) : void 0; - const onError = executeOptions.onError || current.options?.onError; + const onError = + executeOptions.onError || ref.current.options?.onError; if (error && onError) { onError( @@ -156,7 +155,7 @@ export function useMutation< } if ( - mutationId === current.mutationId && + mutationId === ref.current.mutationId && !clientOptions.ignoreResults ) { const result = { @@ -167,13 +166,13 @@ export function useMutation< client, }; - if (current.isMounted && !equal(current.result, result)) { - setResult((current.result = result)); + if (ref.current.isMounted && !equal(ref.current.result, result)) { + setResult((ref.current.result = result)); } } const onCompleted = - executeOptions.onCompleted || current.options?.onCompleted; + executeOptions.onCompleted || ref.current.options?.onCompleted; if (!error) { onCompleted?.( @@ -185,7 +184,7 @@ export function useMutation< return response; }) .catch((error) => { - if (mutationId === current.mutationId && current.isMounted) { + if (mutationId === ref.current.mutationId && ref.current.isMounted) { const result = { loading: false, error, @@ -194,12 +193,13 @@ export function useMutation< client, }; - if (!equal(current.result, result)) { - setResult((current.result = result)); + if (!equal(ref.current.result, result)) { + setResult((ref.current.result = result)); } } - const onError = executeOptions.onError || current.options?.onError; + const onError = + executeOptions.onError || ref.current.options?.onError; if (onError) { onError( @@ -214,24 +214,29 @@ export function useMutation< throw error; }); }, - [current] + [] ); const reset = React.useCallback(() => { - if (current.isMounted) { - const result = { called: false, loading: false, client }; - Object.assign(current, { mutationId: 0, result }); + if (ref.current.isMounted) { + const result = { + called: false, + loading: false, + client: ref.current.client, + }; + Object.assign(ref.current, { mutationId: 0, result }); setResult(result); } - }, [client, current]); + }, []); React.useEffect(() => { + const current = ref.current; current.isMounted = true; return () => { current.isMounted = false; }; - }, [current]); + }, []); return [execute, { reset, ...result }]; } From 172f26cdd51f90fe4c37af7ab80523aa079e0cc9 Mon Sep 17 00:00:00 2001 From: phryneas Date: Thu, 16 May 2024 15:23:08 +0000 Subject: [PATCH 06/21] Clean up Prettier, Size-limit, and Api-Extractor --- .size-limits.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.size-limits.json b/.size-limits.json index 39967e70332..1a0678b27ed 100644 --- a/.size-limits.json +++ b/.size-limits.json @@ -1,4 +1,4 @@ { - "dist/apollo-client.min.cjs": 39565, + "dist/apollo-client.min.cjs": 39587, "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32821 } From c5a5d28049f7427f19d42b21a93563db618636e1 Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Thu, 16 May 2024 17:51:39 +0200 Subject: [PATCH 07/21] avoid reading/writing ref in render --- src/react/hooks/useQuery.ts | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/react/hooks/useQuery.ts b/src/react/hooks/useQuery.ts index 04f211dedfb..16133598756 100644 --- a/src/react/hooks/useQuery.ts +++ b/src/react/hooks/useQuery.ts @@ -109,23 +109,27 @@ export function useInternalState( client: ApolloClient, query: DocumentNode | TypedDocumentNode ): InternalState { - const stateRef = React.useRef>(); - if ( - !stateRef.current || - client !== stateRef.current.client || - query !== stateRef.current.query - ) { - stateRef.current = new InternalState(client, query, stateRef.current); - } - const state = stateRef.current; - // By default, InternalState.prototype.forceUpdate is an empty function, but // we replace it here (before anyone has had a chance to see this state yet) // with a function that unconditionally forces an update, using the latest // setTick function. Updating this state by calling state.forceUpdate is the // only way we trigger React component updates (no other useState calls within // the InternalState class). - state.forceUpdateState = React.useReducer((tick) => tick + 1, 0)[1]; + const forceUpdateState = React.useReducer((tick) => tick + 1, 0)[1]; + + const stateRef = React.useRef | undefined>( + undefined + ); + const state = React.useMemo( + () => + Object.assign(new InternalState(client, query, stateRef.current), { + forceUpdateState, + }), + [client, forceUpdateState, query] + ); + React.useLayoutEffect(() => { + stateRef.current = state; + }); return state; } From 6070945dab54033bd4979b14373dd169b217390b Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Thu, 16 May 2024 17:53:31 +0200 Subject: [PATCH 08/21] fix up useLazyQuery --- src/react/hooks/useLazyQuery.ts | 26 +++++++++++++++----------- src/react/hooks/useQuery.ts | 2 +- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/react/hooks/useLazyQuery.ts b/src/react/hooks/useLazyQuery.ts index 3f485d5797f..a8d6eb00a67 100644 --- a/src/react/hooks/useLazyQuery.ts +++ b/src/react/hooks/useLazyQuery.ts @@ -94,18 +94,17 @@ export function useLazyQuery< useQueryResult.observable.options.initialFetchPolicy || internalState.getDefaultFetchPolicy(); - useQueryResult.called = !!execOptionsRef.current; - + const { forceUpdateState, obsQueryFields } = internalState; // We use useMemo here to make sure the eager methods have a stable identity. const eagerMethods = React.useMemo(() => { const eagerMethods: Record = {}; for (const key of EAGER_METHODS) { - const method = useQueryResult[key]; + const method = obsQueryFields[key]; eagerMethods[key] = function () { if (!execOptionsRef.current) { execOptionsRef.current = Object.create(null); // Only the first time populating execOptionsRef.current matters here. - internalState.forceUpdateState(); + forceUpdateState(); } // @ts-expect-error this is just too generic to type return method.apply(this, arguments); @@ -113,12 +112,17 @@ export function useLazyQuery< } return eagerMethods; - // React Hook React.useMemo has missing dependencies: 'internalState' and 'useQueryResult'. - // Both `internalState` as well as all of the handlers are stable references. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - Object.assign(useQueryResult, eagerMethods); + }, [forceUpdateState, obsQueryFields]); + + const called = !!execOptionsRef.current; + const result = React.useMemo( + () => ({ + ...useQueryResult, + ...eagerMethods, + called, + }), + [useQueryResult, eagerMethods, called] + ); const execute = React.useCallback[0]>( (executeOptions) => { @@ -150,5 +154,5 @@ export function useLazyQuery< [eagerMethods, initialFetchPolicy, internalState] ); - return [execute, useQueryResult]; + return [execute, result]; } diff --git a/src/react/hooks/useQuery.ts b/src/react/hooks/useQuery.ts index 16133598756..977a7359c7a 100644 --- a/src/react/hooks/useQuery.ts +++ b/src/react/hooks/useQuery.ts @@ -522,7 +522,7 @@ class InternalState { private onError(error: ApolloError) {} private observable!: ObservableQuery; - private obsQueryFields!: Omit< + public obsQueryFields!: Omit< ObservableQueryFields, "variables" >; From 9c78b5ca24bad1dd7ad71a0eefd9054d4cf0040e Mon Sep 17 00:00:00 2001 From: phryneas Date: Thu, 16 May 2024 15:56:19 +0000 Subject: [PATCH 09/21] Clean up Prettier, Size-limit, and Api-Extractor --- .size-limits.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.size-limits.json b/.size-limits.json index 1a0678b27ed..50e4f0e128e 100644 --- a/.size-limits.json +++ b/.size-limits.json @@ -1,4 +1,4 @@ { - "dist/apollo-client.min.cjs": 39587, + "dist/apollo-client.min.cjs": 39619, "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32821 } From 8a338159f36e83894f7b1415d1838170dc20f6aa Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Fri, 17 May 2024 11:20:23 +0200 Subject: [PATCH 10/21] fix up another test doing a mutation in render --- src/react/components/__tests__/client/Mutation.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/react/components/__tests__/client/Mutation.test.tsx b/src/react/components/__tests__/client/Mutation.test.tsx index 80c75f99506..b71ba82f7cc 100644 --- a/src/react/components/__tests__/client/Mutation.test.tsx +++ b/src/react/components/__tests__/client/Mutation.test.tsx @@ -1348,7 +1348,7 @@ describe("General Mutation testing", () => { if (count === 0) { expect(result.called).toEqual(false); expect(result.loading).toEqual(false); - createTodo(); + setTimeout(createTodo, 10); } else if (count === 2 && result) { expect(result.data).toEqual(data); setTimeout(() => { @@ -1358,7 +1358,7 @@ describe("General Mutation testing", () => { }); } else if (count === 3) { expect(result.loading).toEqual(false); - createTodo(); + setTimeout(createTodo, 10); } else if (count === 5) { expect(result.data).toEqual(data3); } From 1aeb9ffff1344f1c88c2031adf7fc0e9e883e519 Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Fri, 17 May 2024 12:45:55 +0200 Subject: [PATCH 11/21] more useQuery cleanup --- src/react/hooks/useQuery.ts | 216 +++++++++++++++++++----------------- 1 file changed, 117 insertions(+), 99 deletions(-) diff --git a/src/react/hooks/useQuery.ts b/src/react/hooks/useQuery.ts index 977a7359c7a..40df312f8d9 100644 --- a/src/react/hooks/useQuery.ts +++ b/src/react/hooks/useQuery.ts @@ -219,111 +219,129 @@ class InternalState { // Methods beginning with use- should be called according to the standard // rules of React hooks: only at the top level of the calling function, and // without any dynamic conditional logic. - useQuery(options: QueryHookOptions) { - // The renderPromises field gets initialized here in the useQuery method, at - // the beginning of everything (for a given component rendering, at least), - // so we can safely use this.renderPromises in other/later InternalState - // methods without worrying it might be uninitialized. Even after - // initialization, this.renderPromises is usually undefined (unless SSR is - // happening), but that's fine as long as it has been initialized that way, - // rather than left uninitialized. - - // React Hook "React.useContext" cannot be called in a class component. - // eslint-disable-next-line react-hooks/rules-of-hooks - this.renderPromises = React.useContext(getApolloContext()).renderPromises; - - this.useOptions(options); - - const obsQuery = this.useObservableQuery(); - const renderPromises = this.renderPromises; + useQuery( + options: QueryHookOptions + ): QueryResult { + // Let's treat `this` as a static external variable here so the lint rule has something to go on. + const internalState = this; - // React Hook "useSyncExternalStore" cannot be called in a class component. // eslint-disable-next-line react-hooks/rules-of-hooks - const result = useSyncExternalStore( - // React Hook "React.useCallback" cannot be called in a class component. - // eslint-disable-next-line react-hooks/rules-of-hooks - React.useCallback( - (handleStoreChange) => { - if (renderPromises) { - return () => {}; - } - - this.forceUpdate = handleStoreChange; - - const onNext = () => { - const previousResult = this.result; - // We use `getCurrentResult()` instead of the onNext argument because - // the values differ slightly. Specifically, loading results will have - // an empty object for data instead of `undefined` for some reason. - const result = obsQuery.getCurrentResult(); - // Make sure we're not attempting to re-render similar results - if ( - previousResult && - previousResult.loading === result.loading && - previousResult.networkStatus === result.networkStatus && - equal(previousResult.data, result.data) - ) { - return; + return useQueryImpl(options); + + // the eslint plugin cannot detect that this is a hook if it's a class property + // (honestly, for good reason!) + // this gets around that. + function useQueryImpl(options: QueryHookOptions) { + // The renderPromises field gets initialized here in the useQuery method, at + // the beginning of everything (for a given component rendering, at least), + // so we can safely use this.renderPromises in other/later InternalState + // methods without worrying it might be uninitialized. Even after + // initialization, this.renderPromises is usually undefined (unless SSR is + // happening), but that's fine as long as it has been initialized that way, + // rather than left uninitialized. + + internalState.renderPromises = + React.useContext(getApolloContext()).renderPromises; + + internalState.useOptions(options); + + const obsQuery = internalState.useObservableQuery(); + + const disableNetworkFetches = internalState.client.disableNetworkFetches; + + const result = useSyncExternalStore( + React.useCallback( + (handleStoreChange) => { + // there is nothing to really do here with `disableNetworkFetches`, + // we need to reference it here to ensure the effect depends on it + // and resubscribes when it changes and the component is re-rendered by chance + disableNetworkFetches; + + // Technically, `renderPromises` should probably be a dependency of + // this effect, but we know that it never changes between renders, so + // we can safely omit it. + if (internalState.renderPromises) { + return () => {}; } - this.setResult(result); - }; - - const onError = (error: Error) => { - subscription.unsubscribe(); - subscription = obsQuery.resubscribeAfterError(onNext, onError); - - if (!hasOwnProperty.call(error, "graphQLErrors")) { - // The error is not a GraphQL error - throw error; - } - - const previousResult = this.result; - if ( - !previousResult || - (previousResult && previousResult.loading) || - !equal(error, previousResult.error) - ) { - this.setResult({ - data: (previousResult && previousResult.data) as TData, - error: error as ApolloError, - loading: false, - networkStatus: NetworkStatus.error, - }); - } - }; - - let subscription = obsQuery.subscribe(onNext, onError); - - // Do the "unsubscribe" with a short delay. - // This way, an existing subscription can be reused without an additional - // request if "unsubscribe" and "resubscribe" to the same ObservableQuery - // happen in very fast succession. - return () => { - setTimeout(() => subscription.unsubscribe()); - this.forceUpdate = () => this.forceUpdateState(); - }; - }, - [ - // We memoize the subscribe function using useCallback and the following - // dependency keys, because the subscribe function reference is all that - // useSyncExternalStore uses internally as a dependency key for the - // useEffect ultimately responsible for the subscription, so we are - // effectively passing this dependency array to that useEffect buried - // inside useSyncExternalStore, as desired. - obsQuery, - renderPromises, - ] - ), - - () => this.getCurrentResult(), - () => this.getCurrentResult() - ); + internalState.forceUpdate = handleStoreChange; + + const onNext = () => { + const previousResult = internalState.result; + // We use `getCurrentResult()` instead of the onNext argument because + // the values differ slightly. Specifically, loading results will have + // an empty object for data instead of `undefined` for some reason. + const result = obsQuery.getCurrentResult(); + // Make sure we're not attempting to re-render similar results + if ( + previousResult && + previousResult.loading === result.loading && + previousResult.networkStatus === result.networkStatus && + equal(previousResult.data, result.data) + ) { + return; + } + + internalState.setResult(result); + }; + + const onError = (error: Error) => { + subscription.unsubscribe(); + subscription = obsQuery.resubscribeAfterError(onNext, onError); + + if (!hasOwnProperty.call(error, "graphQLErrors")) { + // The error is not a GraphQL error + throw error; + } + + const previousResult = internalState.result; + if ( + !previousResult || + (previousResult && previousResult.loading) || + !equal(error, previousResult.error) + ) { + internalState.setResult({ + data: (previousResult && previousResult.data) as TData, + error: error as ApolloError, + loading: false, + networkStatus: NetworkStatus.error, + }); + } + }; + + let subscription = obsQuery.subscribe(onNext, onError); + + // Do the "unsubscribe" with a short delay. + // This way, an existing subscription can be reused without an additional + // request if "unsubscribe" and "resubscribe" to the same ObservableQuery + // happen in very fast succession. + return () => { + setTimeout(() => subscription.unsubscribe()); + internalState.forceUpdate = () => + internalState.forceUpdateState(); + }; + }, + [ + // We memoize the subscribe function using useCallback and the following + // dependency keys, because the subscribe function reference is all that + // useSyncExternalStore uses internally as a dependency key for the + // useEffect ultimately responsible for the subscription, so we are + // effectively passing this dependency array to that useEffect buried + // inside useSyncExternalStore, as desired. + obsQuery, + disableNetworkFetches, + ] + ), + + () => internalState.getCurrentResult(), + () => internalState.getCurrentResult() + ); - // TODO Remove this method when we remove support for options.partialRefetch. - this.unsafeHandlePartialRefetch(result); + // TODO Remove this method when we remove support for options.partialRefetch. + internalState.unsafeHandlePartialRefetch(result); - return this.toQueryResult(result); + return internalState.toQueryResult(result); + } } // These members (except for renderPromises) are all populated by the From c34a48c106f46e128fcdcdeeb4454d5f62109597 Mon Sep 17 00:00:00 2001 From: phryneas Date: Fri, 17 May 2024 10:48:16 +0000 Subject: [PATCH 12/21] Clean up Prettier, Size-limit, and Api-Extractor --- .size-limits.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.size-limits.json b/.size-limits.json index 50e4f0e128e..0ed6a4268ef 100644 --- a/.size-limits.json +++ b/.size-limits.json @@ -1,4 +1,4 @@ { - "dist/apollo-client.min.cjs": 39619, + "dist/apollo-client.min.cjs": 39630, "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32821 } From 34ef7d8e90baa4e193f04dcfdfff2c7e40f2286d Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Fri, 17 May 2024 13:11:28 +0200 Subject: [PATCH 13/21] ignore rule of hook for context access --- src/react/hooks/useQueryRefHandlers.ts | 2 ++ src/react/hooks/useReadQuery.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/react/hooks/useQueryRefHandlers.ts b/src/react/hooks/useQueryRefHandlers.ts index a62149360aa..c5832960b3a 100644 --- a/src/react/hooks/useQueryRefHandlers.ts +++ b/src/react/hooks/useQueryRefHandlers.ts @@ -59,6 +59,8 @@ export function useQueryRefHandlers< // client that's available to us at the current position in the React tree // that ApolloClient will then have the job to recreate a real queryRef from // the transported object + // This is just a context read - it's fine to do this conditionally + // eslint-disable-next-line react-hooks/rules-of-hooks : useApolloClient() )(queryRef); } diff --git a/src/react/hooks/useReadQuery.ts b/src/react/hooks/useReadQuery.ts index 215d367b817..53b8486f650 100644 --- a/src/react/hooks/useReadQuery.ts +++ b/src/react/hooks/useReadQuery.ts @@ -52,6 +52,8 @@ export function useReadQuery( // client that's available to us at the current position in the React tree // that ApolloClient will then have the job to recreate a real queryRef from // the transported object + // This is just a context read - it's fine to do this conditionally + // eslint-disable-next-line react-hooks/rules-of-hooks : useApolloClient() )(queryRef); } From c5bb4972218ac745914c3e355dbc4aba9ee1a572 Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Fri, 17 May 2024 15:22:03 +0200 Subject: [PATCH 14/21] undo changes that were moved into separate PRs --- .../__tests__/client/Mutation.test.tsx | 4 +- .../__tests__/mutations/lifecycle.test.tsx | 4 +- src/react/hooks/useLazyQuery.ts | 26 +- src/react/hooks/useMutation.ts | 17 +- src/react/hooks/useQuery.ts | 243 ++++++++---------- 5 files changed, 127 insertions(+), 167 deletions(-) diff --git a/src/react/components/__tests__/client/Mutation.test.tsx b/src/react/components/__tests__/client/Mutation.test.tsx index b71ba82f7cc..80c75f99506 100644 --- a/src/react/components/__tests__/client/Mutation.test.tsx +++ b/src/react/components/__tests__/client/Mutation.test.tsx @@ -1348,7 +1348,7 @@ describe("General Mutation testing", () => { if (count === 0) { expect(result.called).toEqual(false); expect(result.loading).toEqual(false); - setTimeout(createTodo, 10); + createTodo(); } else if (count === 2 && result) { expect(result.data).toEqual(data); setTimeout(() => { @@ -1358,7 +1358,7 @@ describe("General Mutation testing", () => { }); } else if (count === 3) { expect(result.loading).toEqual(false); - setTimeout(createTodo, 10); + createTodo(); } else if (count === 5) { expect(result.data).toEqual(data3); } diff --git a/src/react/hoc/__tests__/mutations/lifecycle.test.tsx b/src/react/hoc/__tests__/mutations/lifecycle.test.tsx index f6ff711e48d..f74895780a3 100644 --- a/src/react/hoc/__tests__/mutations/lifecycle.test.tsx +++ b/src/react/hoc/__tests__/mutations/lifecycle.test.tsx @@ -92,9 +92,7 @@ describe("graphql(mutation) lifecycle", () => { class Container extends React.Component> { render() { if (this.props.listId !== 2) return null; - setTimeout(() => { - this.props.mutate!().then(() => resolve()); - }); + this.props.mutate!().then(() => resolve()); return null; } } diff --git a/src/react/hooks/useLazyQuery.ts b/src/react/hooks/useLazyQuery.ts index a8d6eb00a67..909bf8a24da 100644 --- a/src/react/hooks/useLazyQuery.ts +++ b/src/react/hooks/useLazyQuery.ts @@ -9,6 +9,7 @@ import type { LazyQueryHookOptions, LazyQueryResultTuple, NoInfer, + QueryResult, } from "../types/types.js"; import { useInternalState } from "./useQuery.js"; import { useApolloClient } from "./useApolloClient.js"; @@ -94,17 +95,20 @@ export function useLazyQuery< useQueryResult.observable.options.initialFetchPolicy || internalState.getDefaultFetchPolicy(); - const { forceUpdateState, obsQueryFields } = internalState; + const result: QueryResult = Object.assign(useQueryResult, { + called: !!execOptionsRef.current, + }); + // We use useMemo here to make sure the eager methods have a stable identity. const eagerMethods = React.useMemo(() => { const eagerMethods: Record = {}; for (const key of EAGER_METHODS) { - const method = obsQueryFields[key]; + const method = result[key]; eagerMethods[key] = function () { if (!execOptionsRef.current) { execOptionsRef.current = Object.create(null); // Only the first time populating execOptionsRef.current matters here. - forceUpdateState(); + internalState.forceUpdateState(); } // @ts-expect-error this is just too generic to type return method.apply(this, arguments); @@ -112,17 +116,9 @@ export function useLazyQuery< } return eagerMethods; - }, [forceUpdateState, obsQueryFields]); - - const called = !!execOptionsRef.current; - const result = React.useMemo( - () => ({ - ...useQueryResult, - ...eagerMethods, - called, - }), - [useQueryResult, eagerMethods, called] - ); + }, []); + + Object.assign(result, eagerMethods); const execute = React.useCallback[0]>( (executeOptions) => { @@ -151,7 +147,7 @@ export function useLazyQuery< return promise; }, - [eagerMethods, initialFetchPolicy, internalState] + [] ); return [execute, result]; diff --git a/src/react/hooks/useMutation.ts b/src/react/hooks/useMutation.ts index 65f0820e015..79825f91524 100644 --- a/src/react/hooks/useMutation.ts +++ b/src/react/hooks/useMutation.ts @@ -99,9 +99,11 @@ export function useMutation< options, }); - React.useLayoutEffect(() => { + // TODO: Trying to assign these in a useEffect or useLayoutEffect breaks + // higher-order components. + { Object.assign(ref.current, { client, options, mutation }); - }); + } const execute = React.useCallback( ( @@ -219,22 +221,17 @@ export function useMutation< const reset = React.useCallback(() => { if (ref.current.isMounted) { - const result = { - called: false, - loading: false, - client: ref.current.client, - }; + const result = { called: false, loading: false, client }; Object.assign(ref.current, { mutationId: 0, result }); setResult(result); } }, []); React.useEffect(() => { - const current = ref.current; - current.isMounted = true; + ref.current.isMounted = true; return () => { - current.isMounted = false; + ref.current.isMounted = false; }; }, []); diff --git a/src/react/hooks/useQuery.ts b/src/react/hooks/useQuery.ts index 40df312f8d9..225577521b4 100644 --- a/src/react/hooks/useQuery.ts +++ b/src/react/hooks/useQuery.ts @@ -109,27 +109,23 @@ export function useInternalState( client: ApolloClient, query: DocumentNode | TypedDocumentNode ): InternalState { + const stateRef = React.useRef>(); + if ( + !stateRef.current || + client !== stateRef.current.client || + query !== stateRef.current.query + ) { + stateRef.current = new InternalState(client, query, stateRef.current); + } + const state = stateRef.current; + // By default, InternalState.prototype.forceUpdate is an empty function, but // we replace it here (before anyone has had a chance to see this state yet) // with a function that unconditionally forces an update, using the latest // setTick function. Updating this state by calling state.forceUpdate is the // only way we trigger React component updates (no other useState calls within // the InternalState class). - const forceUpdateState = React.useReducer((tick) => tick + 1, 0)[1]; - - const stateRef = React.useRef | undefined>( - undefined - ); - const state = React.useMemo( - () => - Object.assign(new InternalState(client, query, stateRef.current), { - forceUpdateState, - }), - [client, forceUpdateState, query] - ); - React.useLayoutEffect(() => { - stateRef.current = state; - }); + state.forceUpdateState = React.useReducer((tick) => tick + 1, 0)[1]; return state; } @@ -219,129 +215,104 @@ class InternalState { // Methods beginning with use- should be called according to the standard // rules of React hooks: only at the top level of the calling function, and // without any dynamic conditional logic. - useQuery( - options: QueryHookOptions - ): QueryResult { - // Let's treat `this` as a static external variable here so the lint rule has something to go on. - const internalState = this; - - // eslint-disable-next-line react-hooks/rules-of-hooks - return useQueryImpl(options); - - // the eslint plugin cannot detect that this is a hook if it's a class property - // (honestly, for good reason!) - // this gets around that. - function useQueryImpl(options: QueryHookOptions) { - // The renderPromises field gets initialized here in the useQuery method, at - // the beginning of everything (for a given component rendering, at least), - // so we can safely use this.renderPromises in other/later InternalState - // methods without worrying it might be uninitialized. Even after - // initialization, this.renderPromises is usually undefined (unless SSR is - // happening), but that's fine as long as it has been initialized that way, - // rather than left uninitialized. - - internalState.renderPromises = - React.useContext(getApolloContext()).renderPromises; - - internalState.useOptions(options); - - const obsQuery = internalState.useObservableQuery(); - - const disableNetworkFetches = internalState.client.disableNetworkFetches; - - const result = useSyncExternalStore( - React.useCallback( - (handleStoreChange) => { - // there is nothing to really do here with `disableNetworkFetches`, - // we need to reference it here to ensure the effect depends on it - // and resubscribes when it changes and the component is re-rendered by chance - disableNetworkFetches; - - // Technically, `renderPromises` should probably be a dependency of - // this effect, but we know that it never changes between renders, so - // we can safely omit it. - if (internalState.renderPromises) { - return () => {}; + useQuery(options: QueryHookOptions) { + // The renderPromises field gets initialized here in the useQuery method, at + // the beginning of everything (for a given component rendering, at least), + // so we can safely use this.renderPromises in other/later InternalState + // methods without worrying it might be uninitialized. Even after + // initialization, this.renderPromises is usually undefined (unless SSR is + // happening), but that's fine as long as it has been initialized that way, + // rather than left uninitialized. + this.renderPromises = React.useContext(getApolloContext()).renderPromises; + + this.useOptions(options); + + const obsQuery = this.useObservableQuery(); + + const result = useSyncExternalStore( + React.useCallback( + (handleStoreChange) => { + if (this.renderPromises) { + return () => {}; + } + + this.forceUpdate = handleStoreChange; + + const onNext = () => { + const previousResult = this.result; + // We use `getCurrentResult()` instead of the onNext argument because + // the values differ slightly. Specifically, loading results will have + // an empty object for data instead of `undefined` for some reason. + const result = obsQuery.getCurrentResult(); + // Make sure we're not attempting to re-render similar results + if ( + previousResult && + previousResult.loading === result.loading && + previousResult.networkStatus === result.networkStatus && + equal(previousResult.data, result.data) + ) { + return; } - internalState.forceUpdate = handleStoreChange; - - const onNext = () => { - const previousResult = internalState.result; - // We use `getCurrentResult()` instead of the onNext argument because - // the values differ slightly. Specifically, loading results will have - // an empty object for data instead of `undefined` for some reason. - const result = obsQuery.getCurrentResult(); - // Make sure we're not attempting to re-render similar results - if ( - previousResult && - previousResult.loading === result.loading && - previousResult.networkStatus === result.networkStatus && - equal(previousResult.data, result.data) - ) { - return; - } - - internalState.setResult(result); - }; - - const onError = (error: Error) => { - subscription.unsubscribe(); - subscription = obsQuery.resubscribeAfterError(onNext, onError); - - if (!hasOwnProperty.call(error, "graphQLErrors")) { - // The error is not a GraphQL error - throw error; - } - - const previousResult = internalState.result; - if ( - !previousResult || - (previousResult && previousResult.loading) || - !equal(error, previousResult.error) - ) { - internalState.setResult({ - data: (previousResult && previousResult.data) as TData, - error: error as ApolloError, - loading: false, - networkStatus: NetworkStatus.error, - }); - } - }; - - let subscription = obsQuery.subscribe(onNext, onError); - - // Do the "unsubscribe" with a short delay. - // This way, an existing subscription can be reused without an additional - // request if "unsubscribe" and "resubscribe" to the same ObservableQuery - // happen in very fast succession. - return () => { - setTimeout(() => subscription.unsubscribe()); - internalState.forceUpdate = () => - internalState.forceUpdateState(); - }; - }, - [ - // We memoize the subscribe function using useCallback and the following - // dependency keys, because the subscribe function reference is all that - // useSyncExternalStore uses internally as a dependency key for the - // useEffect ultimately responsible for the subscription, so we are - // effectively passing this dependency array to that useEffect buried - // inside useSyncExternalStore, as desired. - obsQuery, - disableNetworkFetches, - ] - ), - - () => internalState.getCurrentResult(), - () => internalState.getCurrentResult() - ); + this.setResult(result); + }; - // TODO Remove this method when we remove support for options.partialRefetch. - internalState.unsafeHandlePartialRefetch(result); + const onError = (error: Error) => { + subscription.unsubscribe(); + subscription = obsQuery.resubscribeAfterError(onNext, onError); - return internalState.toQueryResult(result); - } + if (!hasOwnProperty.call(error, "graphQLErrors")) { + // The error is not a GraphQL error + throw error; + } + + const previousResult = this.result; + if ( + !previousResult || + (previousResult && previousResult.loading) || + !equal(error, previousResult.error) + ) { + this.setResult({ + data: (previousResult && previousResult.data) as TData, + error: error as ApolloError, + loading: false, + networkStatus: NetworkStatus.error, + }); + } + }; + + let subscription = obsQuery.subscribe(onNext, onError); + + // Do the "unsubscribe" with a short delay. + // This way, an existing subscription can be reused without an additional + // request if "unsubscribe" and "resubscribe" to the same ObservableQuery + // happen in very fast succession. + return () => { + setTimeout(() => subscription.unsubscribe()); + this.forceUpdate = () => this.forceUpdateState(); + }; + }, + [ + // We memoize the subscribe function using useCallback and the following + // dependency keys, because the subscribe function reference is all that + // useSyncExternalStore uses internally as a dependency key for the + // useEffect ultimately responsible for the subscription, so we are + // effectively passing this dependency array to that useEffect buried + // inside useSyncExternalStore, as desired. + obsQuery, + this.renderPromises, + this.client.disableNetworkFetches, + ] + ), + + () => this.getCurrentResult(), + () => this.getCurrentResult() + ); + + // TODO Remove this method when we remove support for options.partialRefetch. + this.unsafeHandlePartialRefetch(result); + + return this.toQueryResult(result); } // These members (except for renderPromises) are all populated by the @@ -540,7 +511,7 @@ class InternalState { private onError(error: ApolloError) {} private observable!: ObservableQuery; - public obsQueryFields!: Omit< + private obsQueryFields!: Omit< ObservableQueryFields, "variables" >; @@ -555,8 +526,6 @@ class InternalState { this.observable || // Reuse this.observable if possible (and not SSR) this.client.watchQuery(this.getObsQueryOptions())); - // React Hook "React.useMemo" cannot be called in a class component. - // eslint-disable-next-line react-hooks/rules-of-hooks this.obsQueryFields = React.useMemo( () => ({ refetch: obsQuery.refetch.bind(obsQuery), From 8cdba15be1737980db5c53522c53196f0871f150 Mon Sep 17 00:00:00 2001 From: phryneas Date: Fri, 17 May 2024 13:24:19 +0000 Subject: [PATCH 15/21] Clean up Prettier, Size-limit, and Api-Extractor --- .size-limits.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.size-limits.json b/.size-limits.json index 0ed6a4268ef..d34e9ea1f7a 100644 --- a/.size-limits.json +++ b/.size-limits.json @@ -1,4 +1,4 @@ { - "dist/apollo-client.min.cjs": 39630, + "dist/apollo-client.min.cjs": 39571, "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32821 } From 2e751e1a7d32f98768bfedf1735382f4eeb1e7d6 Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Fri, 17 May 2024 18:30:57 +0200 Subject: [PATCH 16/21] almost full rewrite of the `useSubscription` hook --- src/react/hooks/useSubscription.ts | 329 +++++++++++++++++------------ 1 file changed, 190 insertions(+), 139 deletions(-) diff --git a/src/react/hooks/useSubscription.ts b/src/react/hooks/useSubscription.ts index c441e7c324f..5f626f089c4 100644 --- a/src/react/hooks/useSubscription.ts +++ b/src/react/hooks/useSubscription.ts @@ -10,8 +10,18 @@ import type { SubscriptionHookOptions, SubscriptionResult, } from "../types/types.js"; -import type { OperationVariables } from "../../core/index.js"; +import type { + ApolloClient, + DefaultContext, + FetchPolicy, + OperationVariables, +} from "../../core/index.js"; import { useApolloClient } from "./useApolloClient.js"; +import { useDeepMemo } from "./internal/useDeepMemo.js"; +import { useSyncExternalStore } from "./useSyncExternalStore.js"; + +const emptyObject = Object.freeze({}); + /** * > Refer to the [Subscriptions](https://www.apollographql.com/docs/react/data/subscriptions/) section for a more in-depth overview of `useSubscription`. * @@ -102,24 +112,19 @@ export function useSubscription< TVariables extends OperationVariables = OperationVariables, >( subscription: DocumentNode | TypedDocumentNode, - options?: SubscriptionHookOptions, NoInfer> + options: SubscriptionHookOptions< + NoInfer, + NoInfer + > = emptyObject ) { const hasIssuedDeprecationWarningRef = React.useRef(false); - const client = useApolloClient(options?.client); + const client = useApolloClient(options.client); verifyDocumentType(subscription, DocumentType.Subscription); - const [result, setResult] = React.useState< - SubscriptionResult - >({ - loading: !options?.skip, - error: void 0, - data: void 0, - variables: options?.variables, - }); if (!hasIssuedDeprecationWarningRef.current) { hasIssuedDeprecationWarningRef.current = true; - if (options?.onSubscriptionData) { + if (options.onSubscriptionData) { invariant.warn( options.onData ? "'useSubscription' supports only the 'onSubscriptionData' or 'onData' option, but not both. Only the 'onData' option will be used." @@ -127,7 +132,7 @@ export function useSubscription< ); } - if (options?.onSubscriptionComplete) { + if (options.onSubscriptionComplete) { invariant.warn( options.onComplete ? "'useSubscription' supports only the 'onSubscriptionComplete' or 'onComplete' option, but not both. Only the 'onComplete' option will be used." @@ -136,146 +141,192 @@ export function useSubscription< } } - const [observable, setObservable] = React.useState(() => { - if (options?.skip) { - return null; - } - - return client.subscribe({ - query: subscription, - variables: options?.variables, - fetchPolicy: options?.fetchPolicy, - context: options?.context, - }); - }); + let { skip, fetchPolicy, variables, shouldResubscribe, context } = options; + variables = useDeepMemo(() => variables, [variables]); + if (typeof shouldResubscribe === "function") { + shouldResubscribe = !!shouldResubscribe(options!); + } - const canResetObservableRef = React.useRef(false); + const contextRef = React.useRef(context); React.useEffect(() => { - return () => { - canResetObservableRef.current = true; - }; - }, []); + contextRef.current = context; + }, [context]); - const ref = React.useRef({ client, subscription, options }); - React.useEffect(() => { - let shouldResubscribe = options?.shouldResubscribe; - if (typeof shouldResubscribe === "function") { - shouldResubscribe = !!shouldResubscribe(options!); - } + const [observable, setObservable] = React.useState(() => + options.skip ? null : ( + createSubscription( + client, + subscription, + variables, + fetchPolicy, + contextRef.current + ) + ) + ); - if (options?.skip) { - if ( - !options?.skip !== !ref.current.options?.skip || - canResetObservableRef.current + React.useEffect(() => { + function resubscriptionEffect() { + if (skip) { + if (observable) { + setObservable(null); + } + } else if ( + !observable || + (shouldResubscribe !== false && + (client !== observable.__.client || + subscription !== observable.__.query || + !equal(variables, observable.__.variables) || + fetchPolicy !== observable.__.fetchPolicy)) ) { - setResult({ - loading: false, - data: void 0, - error: void 0, - variables: options?.variables, - }); - setObservable(null); - canResetObservableRef.current = false; + setObservable( + createSubscription( + client, + subscription, + variables, + fetchPolicy, + contextRef.current + ) + ); } - } else if ( - (shouldResubscribe !== false && - (client !== ref.current.client || - subscription !== ref.current.subscription || - options?.fetchPolicy !== ref.current.options?.fetchPolicy || - !options?.skip !== !ref.current.options?.skip || - !equal(options?.variables, ref.current.options?.variables))) || - canResetObservableRef.current - ) { - setResult({ - loading: true, - data: void 0, - error: void 0, - variables: options?.variables, - }); - setObservable( - client.subscribe({ - query: subscription, - variables: options?.variables, - fetchPolicy: options?.fetchPolicy, - context: options?.context, - }) - ); - canResetObservableRef.current = false; } + const id = setTimeout(resubscriptionEffect); + return () => clearTimeout(id); + }, [ + client, + fetchPolicy, + observable, + shouldResubscribe, + skip, + subscription, + variables, + ]); - Object.assign(ref.current, { client, subscription, options }); - }, [client, subscription, options]); - + const optionsRef = React.useRef(options); React.useEffect(() => { - if (!observable) { - return; - } + optionsRef.current = options; + }); + + const fallbackResult = React.useMemo>( + () => ({ + loading: !skip, + error: void 0, + data: void 0, + variables: variables, + }), + [skip, variables] + ); - let subscriptionStopped = false; - const subscription = observable.subscribe({ - next(fetchResult) { - if (subscriptionStopped) { - return; + return useSyncExternalStore>( + React.useCallback( + (update) => { + if (!observable) { + return () => {}; } - const result = { - loading: false, - // TODO: fetchResult.data can be null but SubscriptionResult.data - // expects TData | undefined only - data: fetchResult.data!, - error: void 0, - variables: options?.variables, - }; - setResult(result); + let subscriptionStopped = false; + const variables = observable.__.variables; + const client = observable.__.client; + const subscription = observable.subscribe({ + next(fetchResult) { + if (subscriptionStopped) { + return; + } - if (ref.current.options?.onData) { - ref.current.options.onData({ - client, - data: result, - }); - } else if (ref.current.options?.onSubscriptionData) { - ref.current.options.onSubscriptionData({ - client, - subscriptionData: result, - }); - } - }, - error(error) { - if (!subscriptionStopped) { - setResult({ - loading: false, - data: void 0, - error, - variables: options?.variables, + const result = { + loading: false, + // TODO: fetchResult.data can be null but SubscriptionResult.data + // expects TData | undefined only + data: fetchResult.data!, + error: void 0, + variables, + }; + observable.__.result = result; + update(); + + if (optionsRef.current.onData) { + optionsRef.current.onData({ + client, + data: result, + }); + } else if (optionsRef.current.onSubscriptionData) { + optionsRef.current.onSubscriptionData({ + client, + subscriptionData: result, + }); + } + }, + error(error) { + if (!subscriptionStopped) { + observable.__.result = { + loading: false, + data: void 0, + error, + variables, + }; + update(); + optionsRef.current.onError?.(error); + } + }, + complete() { + if (!subscriptionStopped) { + if (optionsRef.current.onComplete) { + optionsRef.current.onComplete(); + } else if (optionsRef.current.onSubscriptionComplete) { + optionsRef.current.onSubscriptionComplete(); + } + } + }, + }); + + return () => { + // immediately stop receiving subscription values, but do not unsubscribe + // until after a short delay in case another useSubscription hook is + // reusing the same underlying observable and is about to subscribe + subscriptionStopped = true; + setTimeout(() => { + subscription.unsubscribe(); }); - ref.current.options?.onError?.(error); - } - }, - complete() { - if (!subscriptionStopped) { - if (ref.current.options?.onComplete) { - ref.current.options.onComplete(); - } else if (ref.current.options?.onSubscriptionComplete) { - ref.current.options.onSubscriptionComplete(); - } - } + }; }, - }); - - return () => { - // immediately stop receiving subscription values, but do not unsubscribe - // until after a short delay in case another useSubscription hook is - // reusing the same underlying observable and is about to subscribe - subscriptionStopped = true; - setTimeout(() => { - subscription.unsubscribe(); - }); - }; - // React Hook React.useEffect has a missing dependency: 'options?.variables'. - // This seems like a valid complaint and we should investigate that. - // Generally: why is this a second `useEffect` and not part of the first one? - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [client, observable]); + [observable] + ), + React.useCallback( + () => (observable && !skip ? observable.__.result : fallbackResult), + [observable, fallbackResult, skip] + ) + ); +} - return result; +function createSubscription< + TData = any, + TVariables extends OperationVariables = OperationVariables, +>( + client: ApolloClient, + query: TypedDocumentNode, + variables?: TVariables, + fetchPolicy?: FetchPolicy, + context?: DefaultContext +) { + return Object.assign( + client.subscribe({ + query, + variables, + fetchPolicy, + context, + }), + { + __: { + variables, + client, + query, + fetchPolicy, + result: { + loading: true, + data: void 0, + error: void 0, + variables, + } as SubscriptionResult, + }, + } + ); } From d70b45e429179bcf79bea23b96d2300453353706 Mon Sep 17 00:00:00 2001 From: phryneas Date: Fri, 17 May 2024 16:33:15 +0000 Subject: [PATCH 17/21] Clean up Prettier, Size-limit, and Api-Extractor --- .size-limits.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.size-limits.json b/.size-limits.json index d34e9ea1f7a..44d356a4992 100644 --- a/.size-limits.json +++ b/.size-limits.json @@ -1,4 +1,4 @@ { - "dist/apollo-client.min.cjs": 39571, + "dist/apollo-client.min.cjs": 39586, "import { ApolloClient, InMemoryCache, HttpLink } from \"dist/index.js\" (production)": 32821 } From 0c813a108b5450786fce123c5898cd150564314a Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Wed, 22 May 2024 16:46:06 +0200 Subject: [PATCH 18/21] ignore any rule violation lint warnings in useSubscription and useQuery --- src/react/hooks/useQuery.ts | 6 + src/react/hooks/useSubscription.ts | 327 ++++++++++++----------------- 2 files changed, 143 insertions(+), 190 deletions(-) diff --git a/src/react/hooks/useQuery.ts b/src/react/hooks/useQuery.ts index 225577521b4..4e6b854e1ee 100644 --- a/src/react/hooks/useQuery.ts +++ b/src/react/hooks/useQuery.ts @@ -223,13 +223,16 @@ class InternalState { // initialization, this.renderPromises is usually undefined (unless SSR is // happening), but that's fine as long as it has been initialized that way, // rather than left uninitialized. + // eslint-disable-next-line react-hooks/rules-of-hooks this.renderPromises = React.useContext(getApolloContext()).renderPromises; this.useOptions(options); const obsQuery = this.useObservableQuery(); + // eslint-disable-next-line react-hooks/rules-of-hooks const result = useSyncExternalStore( + // eslint-disable-next-line react-hooks/rules-of-hooks React.useCallback( (handleStoreChange) => { if (this.renderPromises) { @@ -300,7 +303,9 @@ class InternalState { // effectively passing this dependency array to that useEffect buried // inside useSyncExternalStore, as desired. obsQuery, + // eslint-disable-next-line react-hooks/exhaustive-deps this.renderPromises, + // eslint-disable-next-line react-hooks/exhaustive-deps this.client.disableNetworkFetches, ] ), @@ -526,6 +531,7 @@ class InternalState { this.observable || // Reuse this.observable if possible (and not SSR) this.client.watchQuery(this.getObsQueryOptions())); + // eslint-disable-next-line react-hooks/rules-of-hooks this.obsQueryFields = React.useMemo( () => ({ refetch: obsQuery.refetch.bind(obsQuery), diff --git a/src/react/hooks/useSubscription.ts b/src/react/hooks/useSubscription.ts index 5f626f089c4..2ca0515f060 100644 --- a/src/react/hooks/useSubscription.ts +++ b/src/react/hooks/useSubscription.ts @@ -10,18 +10,8 @@ import type { SubscriptionHookOptions, SubscriptionResult, } from "../types/types.js"; -import type { - ApolloClient, - DefaultContext, - FetchPolicy, - OperationVariables, -} from "../../core/index.js"; +import type { OperationVariables } from "../../core/index.js"; import { useApolloClient } from "./useApolloClient.js"; -import { useDeepMemo } from "./internal/useDeepMemo.js"; -import { useSyncExternalStore } from "./useSyncExternalStore.js"; - -const emptyObject = Object.freeze({}); - /** * > Refer to the [Subscriptions](https://www.apollographql.com/docs/react/data/subscriptions/) section for a more in-depth overview of `useSubscription`. * @@ -112,19 +102,24 @@ export function useSubscription< TVariables extends OperationVariables = OperationVariables, >( subscription: DocumentNode | TypedDocumentNode, - options: SubscriptionHookOptions< - NoInfer, - NoInfer - > = emptyObject + options?: SubscriptionHookOptions, NoInfer> ) { const hasIssuedDeprecationWarningRef = React.useRef(false); - const client = useApolloClient(options.client); + const client = useApolloClient(options?.client); verifyDocumentType(subscription, DocumentType.Subscription); + const [result, setResult] = React.useState< + SubscriptionResult + >({ + loading: !options?.skip, + error: void 0, + data: void 0, + variables: options?.variables, + }); if (!hasIssuedDeprecationWarningRef.current) { hasIssuedDeprecationWarningRef.current = true; - if (options.onSubscriptionData) { + if (options?.onSubscriptionData) { invariant.warn( options.onData ? "'useSubscription' supports only the 'onSubscriptionData' or 'onData' option, but not both. Only the 'onData' option will be used." @@ -132,7 +127,7 @@ export function useSubscription< ); } - if (options.onSubscriptionComplete) { + if (options?.onSubscriptionComplete) { invariant.warn( options.onComplete ? "'useSubscription' supports only the 'onSubscriptionComplete' or 'onComplete' option, but not both. Only the 'onComplete' option will be used." @@ -141,192 +136,144 @@ export function useSubscription< } } - let { skip, fetchPolicy, variables, shouldResubscribe, context } = options; - variables = useDeepMemo(() => variables, [variables]); - if (typeof shouldResubscribe === "function") { - shouldResubscribe = !!shouldResubscribe(options!); - } + const [observable, setObservable] = React.useState(() => { + if (options?.skip) { + return null; + } - const contextRef = React.useRef(context); - React.useEffect(() => { - contextRef.current = context; - }, [context]); + return client.subscribe({ + query: subscription, + variables: options?.variables, + fetchPolicy: options?.fetchPolicy, + context: options?.context, + }); + }); - const [observable, setObservable] = React.useState(() => - options.skip ? null : ( - createSubscription( - client, - subscription, - variables, - fetchPolicy, - contextRef.current - ) - ) - ); + const canResetObservableRef = React.useRef(false); + React.useEffect(() => { + return () => { + canResetObservableRef.current = true; + }; + }, []); + const ref = React.useRef({ client, subscription, options }); React.useEffect(() => { - function resubscriptionEffect() { - if (skip) { - if (observable) { - setObservable(null); - } - } else if ( - !observable || - (shouldResubscribe !== false && - (client !== observable.__.client || - subscription !== observable.__.query || - !equal(variables, observable.__.variables) || - fetchPolicy !== observable.__.fetchPolicy)) + let shouldResubscribe = options?.shouldResubscribe; + if (typeof shouldResubscribe === "function") { + shouldResubscribe = !!shouldResubscribe(options!); + } + + if (options?.skip) { + if ( + !options?.skip !== !ref.current.options?.skip || + canResetObservableRef.current ) { - setObservable( - createSubscription( - client, - subscription, - variables, - fetchPolicy, - contextRef.current - ) - ); + setResult({ + loading: false, + data: void 0, + error: void 0, + variables: options?.variables, + }); + setObservable(null); + canResetObservableRef.current = false; } + } else if ( + (shouldResubscribe !== false && + (client !== ref.current.client || + subscription !== ref.current.subscription || + options?.fetchPolicy !== ref.current.options?.fetchPolicy || + !options?.skip !== !ref.current.options?.skip || + !equal(options?.variables, ref.current.options?.variables))) || + canResetObservableRef.current + ) { + setResult({ + loading: true, + data: void 0, + error: void 0, + variables: options?.variables, + }); + setObservable( + client.subscribe({ + query: subscription, + variables: options?.variables, + fetchPolicy: options?.fetchPolicy, + context: options?.context, + }) + ); + canResetObservableRef.current = false; } - const id = setTimeout(resubscriptionEffect); - return () => clearTimeout(id); - }, [ - client, - fetchPolicy, - observable, - shouldResubscribe, - skip, - subscription, - variables, - ]); - const optionsRef = React.useRef(options); - React.useEffect(() => { - optionsRef.current = options; - }); + Object.assign(ref.current, { client, subscription, options }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [client, subscription, options, canResetObservableRef.current]); - const fallbackResult = React.useMemo>( - () => ({ - loading: !skip, - error: void 0, - data: void 0, - variables: variables, - }), - [skip, variables] - ); + React.useEffect(() => { + if (!observable) { + return; + } - return useSyncExternalStore>( - React.useCallback( - (update) => { - if (!observable) { - return () => {}; + let subscriptionStopped = false; + const subscription = observable.subscribe({ + next(fetchResult) { + if (subscriptionStopped) { + return; } - let subscriptionStopped = false; - const variables = observable.__.variables; - const client = observable.__.client; - const subscription = observable.subscribe({ - next(fetchResult) { - if (subscriptionStopped) { - return; - } - - const result = { - loading: false, - // TODO: fetchResult.data can be null but SubscriptionResult.data - // expects TData | undefined only - data: fetchResult.data!, - error: void 0, - variables, - }; - observable.__.result = result; - update(); - - if (optionsRef.current.onData) { - optionsRef.current.onData({ - client, - data: result, - }); - } else if (optionsRef.current.onSubscriptionData) { - optionsRef.current.onSubscriptionData({ - client, - subscriptionData: result, - }); - } - }, - error(error) { - if (!subscriptionStopped) { - observable.__.result = { - loading: false, - data: void 0, - error, - variables, - }; - update(); - optionsRef.current.onError?.(error); - } - }, - complete() { - if (!subscriptionStopped) { - if (optionsRef.current.onComplete) { - optionsRef.current.onComplete(); - } else if (optionsRef.current.onSubscriptionComplete) { - optionsRef.current.onSubscriptionComplete(); - } - } - }, - }); + const result = { + loading: false, + // TODO: fetchResult.data can be null but SubscriptionResult.data + // expects TData | undefined only + data: fetchResult.data!, + error: void 0, + variables: options?.variables, + }; + setResult(result); - return () => { - // immediately stop receiving subscription values, but do not unsubscribe - // until after a short delay in case another useSubscription hook is - // reusing the same underlying observable and is about to subscribe - subscriptionStopped = true; - setTimeout(() => { - subscription.unsubscribe(); + if (ref.current.options?.onData) { + ref.current.options.onData({ + client, + data: result, }); - }; + } else if (ref.current.options?.onSubscriptionData) { + ref.current.options.onSubscriptionData({ + client, + subscriptionData: result, + }); + } }, - [observable] - ), - React.useCallback( - () => (observable && !skip ? observable.__.result : fallbackResult), - [observable, fallbackResult, skip] - ) - ); -} - -function createSubscription< - TData = any, - TVariables extends OperationVariables = OperationVariables, ->( - client: ApolloClient, - query: TypedDocumentNode, - variables?: TVariables, - fetchPolicy?: FetchPolicy, - context?: DefaultContext -) { - return Object.assign( - client.subscribe({ - query, - variables, - fetchPolicy, - context, - }), - { - __: { - variables, - client, - query, - fetchPolicy, - result: { - loading: true, - data: void 0, - error: void 0, - variables, - } as SubscriptionResult, + error(error) { + if (!subscriptionStopped) { + setResult({ + loading: false, + data: void 0, + error, + variables: options?.variables, + }); + ref.current.options?.onError?.(error); + } }, - } - ); + complete() { + if (!subscriptionStopped) { + if (ref.current.options?.onComplete) { + ref.current.options.onComplete(); + } else if (ref.current.options?.onSubscriptionComplete) { + ref.current.options.onSubscriptionComplete(); + } + } + }, + }); + + return () => { + // immediately stop receiving subscription values, but do not unsubscribe + // until after a short delay in case another useSubscription hook is + // reusing the same underlying observable and is about to subscribe + subscriptionStopped = true; + setTimeout(() => { + subscription.unsubscribe(); + }); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [observable]); + + return result; } From e9c247bbf722ce411fff555dbd06015ab9cceba6 Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Wed, 22 May 2024 16:48:58 +0200 Subject: [PATCH 19/21] changeset --- .changeset/famous-camels-rescue.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/famous-camels-rescue.md diff --git a/.changeset/famous-camels-rescue.md b/.changeset/famous-camels-rescue.md new file mode 100644 index 00000000000..fbf8b08722b --- /dev/null +++ b/.changeset/famous-camels-rescue.md @@ -0,0 +1,5 @@ +--- +"@apollo/client": patch +--- + +`useLoadableQuery`: ensure that `loadQuery` is updated if the ApolloClient instance changes From 619b1887c561f8c490591bee3921de2e03df02b1 Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Wed, 22 May 2024 16:52:19 +0200 Subject: [PATCH 20/21] rename patch --- ...ct-hooks+4.6.0.patch => eslint-plugin-react-hooks+4.6.2.patch} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename patches/{eslint-plugin-react-hooks+4.6.0.patch => eslint-plugin-react-hooks+4.6.2.patch} (100%) diff --git a/patches/eslint-plugin-react-hooks+4.6.0.patch b/patches/eslint-plugin-react-hooks+4.6.2.patch similarity index 100% rename from patches/eslint-plugin-react-hooks+4.6.0.patch rename to patches/eslint-plugin-react-hooks+4.6.2.patch From 694452fe56f485ef7c2dca62b73408954ebcb840 Mon Sep 17 00:00:00 2001 From: Lenz Weber-Tronic Date: Mon, 27 May 2024 17:16:55 +0200 Subject: [PATCH 21/21] also add "eslint-plugin-react-compiler" rules --- .eslintrc | 2 + package-lock.json | 523 ++++++++++++++------- package.json | 1 + src/react/hooks/internal/useRenderGuard.ts | 1 + src/react/hooks/useQueryRefHandlers.ts | 4 +- src/react/hooks/useReadQuery.ts | 4 +- src/react/hooks/useSubscription.ts | 2 + 7 files changed, 360 insertions(+), 177 deletions(-) diff --git a/.eslintrc b/.eslintrc index a15c961ab3b..b4d6d6f5363 100644 --- a/.eslintrc +++ b/.eslintrc @@ -27,7 +27,9 @@ "parserOptions": { "project": "./tsconfig.json" }, + "plugins": ["eslint-plugin-react-compiler"], "rules": { + "react-compiler/react-compiler": "error", "@typescript-eslint/consistent-type-imports": [ "error", { diff --git a/package-lock.json b/package-lock.json index 607d44d4a99..5946e22c36a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -67,6 +67,7 @@ "eslint-import-resolver-typescript": "3.6.1", "eslint-plugin-import": "npm:@phryneas/eslint-plugin-import@2.27.5-pr.2813.2817.199971c", "eslint-plugin-local-rules": "2.0.1", + "eslint-plugin-react-compiler": "^0.0.0-experimental-c8b3f72-20240517", "eslint-plugin-react-hooks": "4.6.2", "eslint-plugin-testing-library": "6.2.2", "expect-type": "0.19.0", @@ -268,78 +269,43 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", - "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.6.tgz", + "integrity": "sha512-ZJhac6FkEd1yhG2AHOmfcXG4ceoLltoCVJjN5XsWN9BifBQr+cHJbWi0h68HZuSORq+3WtJ2z0hwF2NG1b5kcA==", "dev": true, "dependencies": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" + "@babel/highlight": "^7.24.6", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/code-frame/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/compat-data": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", - "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.6.tgz", + "integrity": "sha512-aC2DGhBq5eEdyXWqrDInSqQjO0k8xtPRf5YylULqx8MCd6jBtzqfta/3ETMRpuKIc5hyswfO80ObyA1MvkCcUQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.7.tgz", - "integrity": "sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.6.tgz", + "integrity": "sha512-qAHSfAdVyFmIvl0VHELib8xar7ONuSHrE2hLnsaWkYNTI68dmi1x8GYDhJjMI/e7XWal9QBlZkwbOnkcw7Z8gQ==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.7", - "@babel/parser": "^7.23.6", - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.7", - "@babel/types": "^7.23.6", + "@babel/code-frame": "^7.24.6", + "@babel/generator": "^7.24.6", + "@babel/helper-compilation-targets": "^7.24.6", + "@babel/helper-module-transforms": "^7.24.6", + "@babel/helpers": "^7.24.6", + "@babel/parser": "^7.24.6", + "@babel/template": "^7.24.6", + "@babel/traverse": "^7.24.6", + "@babel/types": "^7.24.6", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -354,6 +320,18 @@ "url": "https://opencollective.com/babel" } }, + "node_modules/@babel/core/node_modules/@babel/parser": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.6.tgz", + "integrity": "sha512-eNZXdfU35nJC2h24RznROuOpO94h6x8sg9ju0tT9biNtLZ2vuP8SduLqqV+/8+cebSLV9SJEAN5Z3zQbJG/M+Q==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@babel/core/node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -361,14 +339,14 @@ "dev": true }, "node_modules/@babel/generator": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", - "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.6.tgz", + "integrity": "sha512-S7m4eNa6YAPJRHmKsLHIDJhNAGNKoWNiWefz1MBbpnt8g9lvMDl1hir4P9bo/57bQEmuwEhnRU/AMWsD0G/Fbg==", "dev": true, "dependencies": { - "@babel/types": "^7.23.6", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", + "@babel/types": "^7.24.6", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" }, "engines": { @@ -376,23 +354,35 @@ } }, "node_modules/@babel/generator/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", - "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.6.tgz", + "integrity": "sha512-DitEzDfOMnd13kZnDqns1ccmftwJTS9DMkyn9pYTxulS7bZxUxpMly3Nf23QQ6NwA4UB8lAqjbqWtyvElEMAkg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", - "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.6.tgz", + "integrity": "sha512-VZQ57UsDGlX/5fFA7GkVPplZhHsVc+vuErWgdOiysI9Ksnw0Pbbd6pnPiR/mmJyKHgyIW0c7KT32gmhiF+cirg==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-validator-option": "^7.23.5", + "@babel/compat-data": "^7.24.6", + "@babel/helper-validator-option": "^7.24.6", "browserslist": "^4.22.2", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -416,63 +406,98 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.6.tgz", + "integrity": "sha512-djsosdPJVZE6Vsw3kk7IPRWethP94WHGOhQTc67SNXE0ZzMhHgALw8iGmYS0TD1bbMM0VDROy43od7/hN6WYcA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.6", + "@babel/helper-environment-visitor": "^7.24.6", + "@babel/helper-function-name": "^7.24.6", + "@babel/helper-member-expression-to-functions": "^7.24.6", + "@babel/helper-optimise-call-expression": "^7.24.6", + "@babel/helper-replace-supers": "^7.24.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.6", + "@babel/helper-split-export-declaration": "^7.24.6", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.6.tgz", + "integrity": "sha512-Y50Cg3k0LKLMjxdPjIl40SdJgMB85iXn27Vk/qbHZCFx/o5XO3PSnpi675h1KEmmDb6OFArfd5SCQEQ5Q4H88g==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.6.tgz", + "integrity": "sha512-xpeLqeeRkbxhnYimfr2PC+iA0Q7ljX/d1eZ9/inYbmfG2jpl8Lu3DyXvpOAnrS5kxkfOWJjioIMQsaMBXFI05w==", "dev": true, "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" + "@babel/template": "^7.24.6", + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.6.tgz", + "integrity": "sha512-SF/EMrC3OD7dSta1bLJIlrsVxwtd0UpjRJqLno6125epQMJ/kyFmpTT4pbvPbdQHzCHg+biQ7Syo8lnDtbR+uA==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.6.tgz", + "integrity": "sha512-OTsCufZTxDUsv2/eDXanw/mUZHWOxSbEmC3pP8cgjcy5rgeVPWWMStnv274DV60JtHxTk0adT0QrCzC4M9NWGg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.6.tgz", + "integrity": "sha512-a26dmxFJBF62rRO9mmpgrfTLsAuyHk4e1hKTUkD/fcMfynt8gvEKwQPQDVxWhca8dHoDck+55DFt42zV0QMw5g==", "dev": true, "dependencies": { - "@babel/types": "^7.22.15" + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", - "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.6.tgz", + "integrity": "sha512-Y/YMPm83mV2HJTbX1Qh2sjgjqcacvOlhbzdCCsSlblOKjSYmQqEbO6rUniWQyRo9ncyfjT8hnUjlG06RXDEmcA==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.20" + "@babel/helper-environment-visitor": "^7.24.6", + "@babel/helper-module-imports": "^7.24.6", + "@babel/helper-simple-access": "^7.24.6", + "@babel/helper-split-export-declaration": "^7.24.6", + "@babel/helper-validator-identifier": "^7.24.6" }, "engines": { "node": ">=6.9.0" @@ -481,6 +506,18 @@ "@babel/core": "^7.0.0" } }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.6.tgz", + "integrity": "sha512-3SFDJRbx7KuPRl8XDUr8O7GAEB8iGyWPjLKJh/ywP/Iy9WOmEfMrsWbaZpvBu2HSYn4KQygIsz0O7m8y10ncMA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-plugin-utils": { "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", @@ -490,80 +527,109 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.6.tgz", + "integrity": "sha512-mRhfPwDqDpba8o1F8ESxsEkJMQkUF8ZIWrAc0FtWhxnjfextxMWxr22RtFizxxSYLjVHDeMgVsRq8BBZR2ikJQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.24.6", + "@babel/helper-member-expression-to-functions": "^7.24.6", + "@babel/helper-optimise-call-expression": "^7.24.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.6.tgz", + "integrity": "sha512-nZzcMMD4ZhmB35MOOzQuiGO5RzL6tJbsT37Zx8M5L/i9KSrukGXWTjLe1knIbb/RmxoJE9GON9soq0c0VEMM5g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.6.tgz", + "integrity": "sha512-jhbbkK3IUKc4T43WadP96a27oYti9gEf1LdyGSP2rHGH77kwLwfhO7TgwnWvxxQVmke0ImmCSS47vcuxEMGD3Q==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.6.tgz", + "integrity": "sha512-CvLSkwXGWnYlF9+J3iZUvwgAxKiYzK3BWuo+mLzD/MDGOZDj7Gq8+hqaOkMxmJwmlv0iu86uH5fdADd9Hxkymw==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.6.tgz", + "integrity": "sha512-WdJjwMEkmBicq5T9fm/cHND3+UlFa2Yj8ALLgmoSQAJZysYbBjw+azChSGPN4DSPLXOcooGRvDwZWMcF/mLO2Q==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.6.tgz", + "integrity": "sha512-4yA7s865JHaqUdRbnaxarZREuPTHrjpDT+pXoAZ1yhyo6uFnIEpS8VMu16siFOHDpZNKYv5BObhsB//ycbICyw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", - "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.6.tgz", + "integrity": "sha512-Jktc8KkF3zIkePb48QO+IapbXlSapOW9S+ogZZkcO6bABgYAxtZcjZ/O005111YLf+j4M84uEgwYoidDkXbCkQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.7.tgz", - "integrity": "sha512-6AMnjCoC8wjqBzDHkuqpa7jAKwvMo4dC+lr/TFBz+ucfulO1XMpDnwWPGBNwClOKZ8h6xn5N81W/R5OrcKtCbQ==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.6.tgz", + "integrity": "sha512-V2PI+NqnyFu1i0GyTd/O/cTpxzQCYioSkUIRmgo7gFEHKKCg5w46+r/A6WeUR1+P3TeQ49dspGPNd/E3n9AnnA==", "dev": true, "dependencies": { - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.7", - "@babel/types": "^7.23.6" + "@babel/template": "^7.24.6", + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.6.tgz", + "integrity": "sha512-2YnuOp4HAk2BsBrJJvYCbItHx0zWscI1C3zgWkz+wDyD9I7GIVrfnLyrR4Y1VR+7p+chAEcrgRQYZAGIKMV7vQ==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-validator-identifier": "^7.24.6", "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" @@ -616,6 +682,23 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/plugin-proposal-private-methods": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead.", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-async-generators": { "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", @@ -806,33 +889,45 @@ } }, "node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.6.tgz", + "integrity": "sha512-3vgazJlLwNXi9jhrR1ef8qiB65L1RK90+lEQwv4OxveHnqC3BfmnHdgySwRLzf6akhlOYenT+b7AfWq+a//AHw==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/code-frame": "^7.24.6", + "@babel/parser": "^7.24.6", + "@babel/types": "^7.24.6" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/template/node_modules/@babel/parser": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.6.tgz", + "integrity": "sha512-eNZXdfU35nJC2h24RznROuOpO94h6x8sg9ju0tT9biNtLZ2vuP8SduLqqV+/8+cebSLV9SJEAN5Z3zQbJG/M+Q==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@babel/traverse": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.7.tgz", - "integrity": "sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.6", - "@babel/types": "^7.23.6", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.6.tgz", + "integrity": "sha512-OsNjaJwT9Zn8ozxcfoBc+RaHdj3gFmCmYoQLUII1o6ZrUwku0BMg80FoOTPx+Gi6XhcQxAYE4xyjPTo4SxEQqw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.6", + "@babel/generator": "^7.24.6", + "@babel/helper-environment-visitor": "^7.24.6", + "@babel/helper-function-name": "^7.24.6", + "@babel/helper-hoist-variables": "^7.24.6", + "@babel/helper-split-export-declaration": "^7.24.6", + "@babel/parser": "^7.24.6", + "@babel/types": "^7.24.6", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -840,14 +935,26 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/traverse/node_modules/@babel/parser": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.6.tgz", + "integrity": "sha512-eNZXdfU35nJC2h24RznROuOpO94h6x8sg9ju0tT9biNtLZ2vuP8SduLqqV+/8+cebSLV9SJEAN5Z3zQbJG/M+Q==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@babel/types": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz", - "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.6.tgz", + "integrity": "sha512-WaMsgi6Q8zMgMth93GvWPXkhAIEobfsIkLTacoVZoK1J0CevIPGYY2Vo5YvJGqyHqXM6P4ppOYGsIRU8MM9pFQ==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-string-parser": "^7.24.6", + "@babel/helper-validator-identifier": "^7.24.6", "to-fast-properties": "^2.0.0" }, "engines": { @@ -2410,19 +2517,29 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" } }, + "node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", @@ -2433,9 +2550,9 @@ } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.1.tgz", - "integrity": "sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, "engines": { "node": ">=6.0.0" @@ -4434,9 +4551,9 @@ "dev": true }, "node_modules/browserslist": { - "version": "4.22.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", - "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", "dev": true, "funding": [ { @@ -4453,8 +4570,8 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001565", - "electron-to-chromium": "^1.4.601", + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", "node-releases": "^2.0.14", "update-browserslist-db": "^1.0.13" }, @@ -4611,9 +4728,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001576", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001576.tgz", - "integrity": "sha512-ff5BdakGe2P3SQsMsiqmt1Lc8221NR1VzHj5jXN5vBny9A6fpze94HiVV/n7XRosOlsShJcvMv5mdnpjOGCEgg==", + "version": "1.0.30001623", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001623.tgz", + "integrity": "sha512-X/XhAVKlpIxWPpgRTnlgZssJrF0m6YtRA0QDWgsBNT12uZM6LPRydR7ip405Y3t1LamD8cP2TZFEDZFBf5ApcA==", "dev": true, "funding": [ { @@ -5361,9 +5478,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.623", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.623.tgz", - "integrity": "sha512-lKoz10iCYlP1WtRYdh5MvocQPWVRoI7ysp6qf18bmeBgR8abE6+I2CsfyNKztRDZvhdWc+krKT6wS7Neg8sw3A==", + "version": "1.4.783", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.783.tgz", + "integrity": "sha512-bT0jEz/Xz1fahQpbZ1D7LgmPYZ3iHVY39NcWWro1+hA2IvjiPeaXtfSqrQ+nXjApMvQRE2ASt1itSLRrebHMRQ==", "dev": true }, "node_modules/emittery": { @@ -5611,9 +5728,9 @@ } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "dev": true, "engines": { "node": ">=6" @@ -5835,6 +5952,26 @@ "integrity": "sha512-AJhGd+GcI5r2dbjiGPixM8jnBl0XFxqoVbqzwKbYz+nTk+Cj5dNE3+OlhC176bl5r25KsGsIthLi1VqIW5Ga+A==", "dev": true }, + "node_modules/eslint-plugin-react-compiler": { + "version": "0.0.0-experimental-c8b3f72-20240517", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-compiler/-/eslint-plugin-react-compiler-0.0.0-experimental-c8b3f72-20240517.tgz", + "integrity": "sha512-cxUTFNMEKiLX6uFaRfrr2GHnB7KUHDMYLjEGzDec82ka6WyBCHg906nGSf3JvVnQKHaBDfUk7Mmv/JMvdgQB8Q==", + "dev": true, + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "@babel/plugin-proposal-private-methods": "^7.18.6", + "hermes-parser": "^0.20.1", + "zod": "^3.22.4", + "zod-validation-error": "^3.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.0.0 || >= 18.0.0" + }, + "peerDependencies": { + "eslint": ">=7" + } + }, "node_modules/eslint-plugin-react-hooks": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", @@ -7058,6 +7195,21 @@ "node": ">= 0.4" } }, + "node_modules/hermes-estree": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.20.1.tgz", + "integrity": "sha512-SQpZK4BzR48kuOg0v4pb3EAGNclzIlqMj3Opu/mu7bbAoFw6oig6cEt/RAi0zTFW/iW6Iz9X9ggGuZTAZ/yZHg==", + "dev": true + }, + "node_modules/hermes-parser": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.20.1.tgz", + "integrity": "sha512-BL5P83cwCogI8D7rrDCgsFY0tdYUtmFP9XaXtl2IQjC+2Xo+4okjfXintlTxcIwl4qeGddEl28Z11kbVIw0aNA==", + "dev": true, + "dependencies": { + "hermes-estree": "0.20.1" + } + }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -10126,9 +10278,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", "dev": true }, "node_modules/picomatch": { @@ -12366,9 +12518,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", + "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==", "dev": true, "funding": [ { @@ -12385,8 +12537,8 @@ } ], "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.1.2", + "picocolors": "^1.0.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -12966,6 +13118,27 @@ "dependencies": { "zen-observable": "0.8.15" } + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-3.3.0.tgz", + "integrity": "sha512-Syib9oumw1NTqEv4LT0e6U83Td9aVRk9iTXPUQr1otyV1PuXQKOvOwhMNqZIq5hluzHP2pMgnOmHEo7kPdI2mw==", + "dev": true, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.18.0" + } } } } diff --git a/package.json b/package.json index 58b728bdbdb..488c0c13b37 100644 --- a/package.json +++ b/package.json @@ -148,6 +148,7 @@ "eslint-import-resolver-typescript": "3.6.1", "eslint-plugin-import": "npm:@phryneas/eslint-plugin-import@2.27.5-pr.2813.2817.199971c", "eslint-plugin-local-rules": "2.0.1", + "eslint-plugin-react-compiler": "0.0.0-experimental-c8b3f72-20240517", "eslint-plugin-react-hooks": "4.6.2", "eslint-plugin-testing-library": "6.2.2", "expect-type": "0.19.0", diff --git a/src/react/hooks/internal/useRenderGuard.ts b/src/react/hooks/internal/useRenderGuard.ts index fbe341d56a9..2d5a798fc3c 100644 --- a/src/react/hooks/internal/useRenderGuard.ts +++ b/src/react/hooks/internal/useRenderGuard.ts @@ -12,6 +12,7 @@ Relay does this too, so we hope this is safe. https://github.com/facebook/relay/blob/8651fbca19adbfbb79af7a3bc40834d105fd7747/packages/react-relay/relay-hooks/loadQuery.js#L90-L98 */ export function useRenderGuard() { + // eslint-disable-next-line react-compiler/react-compiler RenderDispatcher = getRenderDispatcher(); return React.useCallback(() => { diff --git a/src/react/hooks/useQueryRefHandlers.ts b/src/react/hooks/useQueryRefHandlers.ts index c5832960b3a..95036eafcf3 100644 --- a/src/react/hooks/useQueryRefHandlers.ts +++ b/src/react/hooks/useQueryRefHandlers.ts @@ -59,7 +59,9 @@ export function useQueryRefHandlers< // client that's available to us at the current position in the React tree // that ApolloClient will then have the job to recreate a real queryRef from // the transported object - // This is just a context read - it's fine to do this conditionally + // This is just a context read - it's fine to do this conditionally. + // This hook wrapper also shouldn't be optimized by React Compiler. + // eslint-disable-next-line react-compiler/react-compiler // eslint-disable-next-line react-hooks/rules-of-hooks : useApolloClient() )(queryRef); diff --git a/src/react/hooks/useReadQuery.ts b/src/react/hooks/useReadQuery.ts index 53b8486f650..3d6ae811df6 100644 --- a/src/react/hooks/useReadQuery.ts +++ b/src/react/hooks/useReadQuery.ts @@ -52,7 +52,9 @@ export function useReadQuery( // client that's available to us at the current position in the React tree // that ApolloClient will then have the job to recreate a real queryRef from // the transported object - // This is just a context read - it's fine to do this conditionally + // This is just a context read - it's fine to do this conditionally. + // This hook wrapper also shouldn't be optimized by React Compiler. + // eslint-disable-next-line react-compiler/react-compiler // eslint-disable-next-line react-hooks/rules-of-hooks : useApolloClient() )(queryRef); diff --git a/src/react/hooks/useSubscription.ts b/src/react/hooks/useSubscription.ts index 2ca0515f060..0ba57c64346 100644 --- a/src/react/hooks/useSubscription.ts +++ b/src/react/hooks/useSubscription.ts @@ -204,6 +204,7 @@ export function useSubscription< } Object.assign(ref.current, { client, subscription, options }); + // eslint-disable-next-line react-compiler/react-compiler // eslint-disable-next-line react-hooks/exhaustive-deps }, [client, subscription, options, canResetObservableRef.current]); @@ -272,6 +273,7 @@ export function useSubscription< subscription.unsubscribe(); }); }; + // eslint-disable-next-line react-compiler/react-compiler // eslint-disable-next-line react-hooks/exhaustive-deps }, [observable]);