diff --git a/package-lock.json b/package-lock.json index 2bbfb0e1..376057c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,7 @@ "eslint": "^9.36.0", "eslint-config-prettier": "^10.0.1", "eslint-plugin-react": "^7.24.0", - "eslint-plugin-react-hooks": "^6.1.0", + "eslint-plugin-react-hooks": "^7.0.0", "globals": "^16.4.0", "jest": "^30.0.4", "jest-environment-jsdom": "^30.2.0", @@ -149,19 +149,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", - "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.3" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-compilation-targets": { "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", @@ -179,28 +166,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz", - "integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-member-expression-to-functions": "^7.27.1", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.28.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, "node_modules/@babel/helper-globals": { "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", @@ -211,20 +176,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", - "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-module-imports": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", @@ -257,19 +208,6 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", - "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-plugin-utils": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", @@ -280,38 +218,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", - "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.27.1", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", - "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", @@ -372,24 +278,6 @@ "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, - "license": "MIT", - "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", @@ -5011,18 +4899,17 @@ } }, "node_modules/eslint-plugin-react-hooks": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-6.1.0.tgz", - "integrity": "sha512-72mucw/WLzEqGvL2vwE6fWR6geO6UbmDjz3eAb3pezxTpFzgbfyUeFKzmZKr9LhwUWMXfTVh1g0rKEJoyKNdoA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.0.tgz", + "integrity": "sha512-fNXaOwvKwq2+pXiRpXc825Vd63+KM4DLL40Rtlycb8m7fYpp6efrTp1sa6ZbP/Ap58K2bEKFXRmhURE+CJAQWw==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", - "@babel/plugin-proposal-private-methods": "^7.18.6", "hermes-parser": "^0.25.1", - "zod": "^3.22.4", - "zod-validation-error": "^3.0.3" + "zod": "^3.22.4 || ^4.0.0", + "zod-validation-error": "^3.0.3 || ^4.0.0" }, "engines": { "node": ">=18" diff --git a/package.json b/package.json index a208d8df..cea38158 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "eslint": "^9.36.0", "eslint-config-prettier": "^10.0.1", "eslint-plugin-react": "^7.24.0", - "eslint-plugin-react-hooks": "^6.1.0", + "eslint-plugin-react-hooks": "^7.0.0", "globals": "^16.4.0", "jest": "^30.0.4", "jest-environment-jsdom": "^30.2.0", diff --git a/src/components/advanced-marker.tsx b/src/components/advanced-marker.tsx index 2da21910..21b6bbaf 100644 --- a/src/components/advanced-marker.tsx +++ b/src/components/advanced-marker.tsx @@ -1,3 +1,12 @@ +/* eslint-disable react-hooks/immutability */ +// The `react-hooks/immutability` rule is disabled in this file because the +// google.maps.marker.AdvancedMarkerElement object is designed to be mutated +// directly. This is a common pattern when working with imperative APIs like +// the Google Maps JavaScript API. While this goes against the principles of +// immutable state in React, it is a necessary evil to integrate with the +// Google Maps API. The mutations are carefully managed within the `useEffect` +// hooks to ensure that they only happen when the props change. + import type {PropsWithChildren, Ref} from 'react'; import React, { Children, @@ -383,9 +392,10 @@ function useAdvancedMarkerAnchoring( // anchoring of the advanced marker element from the api contentElement.style.transform = `translate(50%, 100%) translate(${translateX}, ${translateY})`; - // We need some kind of flag to identify the custom marker content - // in the infowindow component. Choosing a data attribute to also be able - // to target it via CSS to disable pointer event when using custom anchor point + // data-origin is needed to identify the custom marker content in the + // InfoWindow component as well as in the global CSS used to disable + // the pointer event when anchor points are used in older Google Maps + // versions. marker.dataset.origin = 'rgm'; globalStyleManager.addAdvancedMarkerPointerEventsOverwrite(); diff --git a/src/hooks/use-memoized.ts b/src/hooks/use-memoized.ts index dac5d7eb..9c230c28 100644 --- a/src/hooks/use-memoized.ts +++ b/src/hooks/use-memoized.ts @@ -1,11 +1,12 @@ -import {useRef} from 'react'; +import {useMemo} from 'react'; +import {usePrevious} from './use-previous'; export function useMemoized(value: T, isEqual: (a: T, b: T) => boolean): T { - const ref = useRef(value); - - if (!isEqual(value, ref.current)) { - ref.current = value; - } - - return ref.current; + const previous = usePrevious(value); + return useMemo(() => { + if (previous && isEqual(previous, value)) { + return previous; + } + return value; + }, [value, previous, isEqual]); } diff --git a/src/hooks/use-previous.ts b/src/hooks/use-previous.ts new file mode 100644 index 00000000..aef965f6 --- /dev/null +++ b/src/hooks/use-previous.ts @@ -0,0 +1,15 @@ +import {useEffect, useRef} from 'react'; + +/** + * A hook to store the previous value of a variable. + * @param value The value to store + * @returns The previous value + */ +export function usePrevious(value: T): T | undefined { + const ref = useRef(undefined); + useEffect(() => { + ref.current = value; + }); + // eslint-disable-next-line react-hooks/refs + return ref.current; +} diff --git a/src/hooks/use-prop-binding.ts b/src/hooks/use-prop-binding.ts index f881dc53..1d90213b 100644 --- a/src/hooks/use-prop-binding.ts +++ b/src/hooks/use-prop-binding.ts @@ -17,6 +17,7 @@ export function usePropBinding( useEffect(() => { if (!object) return; + // eslint-disable-next-line react-hooks/immutability object[prop] = value; }, [object, prop, value]); }