Is there any better way to avoid stale closures in React hooks? - reactjs

Let's say we have these requirements to implement:
Display the cursor position as the mouse moves while being held down.
Next time the mouse is down, log to the console the last cursor position when the mouse was previously released.
Here is my solution and it works fine. But I'm not quite happy with it:
https://codesandbox.io/s/funny-orla-kdx19?file=/src/App.js
import React, { useState, useEffect } from "react";
import "./styles.css";
const html = document.documentElement;
export default function App() {
const [cursorPos, setCursorPos] = useState({});
useEffect(() => {
const pointerMove = (e) => {
setCursorPos({ x: e.clientX, y: e.clientY });
};
const pointerUp = (e) => {
html.removeEventListener("pointermove", pointerMove);
html.removeEventListener("pointerup", pointerUp);
};
const pointerDown = () => {
console.log("Last position when pointer up", cursorPos);
html.addEventListener("pointermove", pointerMove);
html.addEventListener("pointerup", pointerUp);
};
console.log("registering pointerdown callback");
html.addEventListener("pointerdown", pointerDown);
return () => {
html.removeEventListener("pointerdown", pointerDown);
};
}, [cursorPos]);
return (
<div className="App">
Tracked mouse position: ({cursorPos.x}, {cursorPos.y})
</div>
);
}
As you can see, the cursorPos dependency is crucial. Without it, the pointerDown listener won't read the latest tracked cursor position. However, it causes the effect callback to be called so often since the cursorPos is updated by the pointermove event. And, the pointerdown listener is deregister/register as a result.
This frequent reregistering of the listener may not pose a practical problem. But it's not conceptually a neat way of doing things. It's an overhead to avoid if possible. That's why I'm not satisfied.
I actually come up with some alternatives which didn't make me fulfilled, either.
Solution 1:
https://codesandbox.io/s/mystifying-lamport-nr3jk?file=/src/App.js
We can log down the cursor position every time the mouse is up and store it in another state variable (cursorPosWhenMouseUp) that is to be read by the next mouse down event. Since the mouse up event is not that frequently triggered, the reregistering frequency is acceptable. The downside of this solution is that the cursorPosWhenMouseUp variable is redundant to my eye because the last cursorPos value should be the same. Its sole purpose is to mitigate a technical issue. It's not elegant to let it stick around in the code.
Solution 2:
https://codesandbox.io/s/upbeat-shamir-rhjs4?file=/src/App.js
We can store another copy of cursorPos in a ref from useRef()(e.g. cursorPosRef). Since ref is stable across rendering and no local variable involved, we can rest assured to safely read it from the pointerDown callback. This solution has similar issues to Solution 1 because we have to attentively make sure the cursorPosRef is all the time mirroring the cursorPos value.
Solution 3:
https://codesandbox.io/s/polished-river-psvz7?file=/src/App.js
In regard to the redundancy issue presented above, we can abandon the cursorPos state variable and only use cursorPosRef. We can directly read it in the JSX. We just need a way to force update the component from the pointermove callback. React Hooks FAQ sheds light on this. The problem with this solution is that the forceUpdate stuff is not welcome in the React methodology.
So, is there any better way to achieve these requirements?

I don't see the need of passing cursorPos as dependency, here is my try:
import React, { useEffect, useState } from 'react';
import './styles.css';
const html = document.documentElement;
export default function App() {
const [cursorPos, setCursorPos] = useState({});
const pointerMove = e => {
setCursorPos({ x: e.clientX, y: e.clientY });
};
useEffect(() => {
html.addEventListener('pointerdown', e => {
html.addEventListener('pointermove', pointerMove);
});
html.addEventListener('pointerup', e => {
html.removeEventListener('pointermove', pointerMove);
});
return () => {
html.removeEventListener('pointerdown');
html.removeEventListener('pointerup');
};
}, []);
return (
<div className="App">
Tracked mouse position: ({cursorPos.x}, {cursorPos.y})
</div>
);
}
https://codesandbox.io/s/eloquent-mendeleev-wqzk4?file=/src/App.js
UPDATE:
To keep track of the previous position I would use useRef:
import React, { useEffect, useRef, useState } from 'react';
const html = document.documentElement;
export default function Test() {
const [cursorCurrentPos, setCursorCurrentPos] = useState({});
const cursorPreviousPos = useRef({});
const pointerMove = e => {
setCursorCurrentPos({ x: e.clientX, y: e.clientY });
};
useEffect(() => {
html.addEventListener('pointerdown', e => {
console.log('Last position when pointer up', cursorPreviousPos.current);
html.addEventListener('pointermove', pointerMove);
});
html.addEventListener('pointerup', e => {
cursorPreviousPos.current = { x: e.clientX, y: e.clientY };
html.removeEventListener('pointermove', pointerMove);
});
return () => {
html.removeEventListener('pointerdown');
html.removeEventListener('pointerup');
};
}, []);
return (
<div className="App">
Tracked mouse position: ({cursorCurrentPos.x}, {cursorCurrentPos.y})
</div>
);
}

Related

React Infinite Loading hook, previous trigger

Im trying to make a hook similar to Waypoint.
I simply want to load items and then when the waypoint is out of screen, allow it to load more items if the waypoint is reached.
I can't seem to figure out the logic to have this work properly.
Currently it see the observer state that its on the screen. then it fetches data rapidly.
I think this is because the hook starts at false everytime. Im not sure how to make it true so the data can load. Followed by the opposite when its reached again.
Any ideas.
Here's the hook:
import { useEffect, useState, useRef, RefObject } from 'react';
export default function useOnScreen(ref: RefObject<HTMLElement>) {
const observerRef = useRef<IntersectionObserver | null>(null);
const [isOnScreen, setIsOnScreen] = useState(false);
useEffect(() => {
observerRef.current = new IntersectionObserver(([entry]) => {
if (isOnScreen !== entry.isIntersecting) {
setIsOnScreen(entry.isIntersecting);
}
});
}, []);
useEffect(() => {
observerRef.current.observe(ref.current);
return () => {
observerRef.current.disconnect();
};
}, [ref]);
return isOnScreen;
}
Here's the use of it:
import React, { useRef } from 'react';
import { WithT } from 'i18next';
import useOnScreen from 'utils/useOnScreen';
interface IInboxListProps extends WithT {
messages: any;
fetchData: () => void;
searchTerm: string;
chatID: string | null;
}
const InboxList: React.FC<IInboxListProps> = ({ messages, fetchData, searchTerm, chatID}) => {
const elementRef = useRef(null);
const isOnScreen = useOnScreen(elementRef);
if (isOnScreen) {
fetchData();
}
const renderItem = () => {
return (
<div className='item unread' key={chatID}>
Item
</div>
);
};
const renderMsgList = ({ messages }) => {
return (
<>
{messages.map(() => {
return renderItem();
})}
</>
);
};
let messagesCopy = [...messages];
//filter results
if (searchTerm !== '') {
messagesCopy = messages.filter(msg => msg.user.toLocaleLowerCase().startsWith(searchTerm.toLocaleLowerCase()));
}
return (
<div className='conversations'>
{renderMsgList({ messages: messagesCopy })}
<div className='item' ref={elementRef} style={{ bottom: '10%', position: 'relative',backgroundColor:"blue",width:"5px",height:"5px" }} />
</div>
);
};
export default InboxList;
Let's inspect this piece of code
const [isOnScreen, setIsOnScreen] = useState(false);
useEffect(() => {
observerRef.current = new IntersectionObserver(([entry]) => {
if (isOnScreen !== entry.isIntersecting) {
setIsOnScreen(entry.isIntersecting);
}
});
}, []);
We have the following meanings:
.isIntersecting is TRUE --> The element became visible
.isIntersecting is FALSE --> The element disappeared
and
isOnScreen is TRUE --> The element was at least once visible
isOnScreen is FALSE--> The element was never visible
When using a xor (!==) you specify that it:
Was never visible and just became visible
this happens 1 time just after the first intersection
Was visible once and now disappeared
this happens n times each time the element is out of the screen
What you want to do is to get more items each time the element intersects
export default function useOnScreen(ref: RefObject<HTMLElement>, onIntersect: function) {
const observerRef = useRef<IntersectionObserver | null>(null);
const [isOnScreen, setIsOnScreen] = useState(false);
useEffect(() => {
observerRef.current = new IntersectionObserver(([entry]) => {
setIsOnScreen(entry.isIntersecting);
});
}, []);
useEffect(()=?{
if(isOnScreen){
onIntersect();
}
},[isOnScreen,onIntersect])
...
}
and then use it like:
const refetch= useCallback(()=>{
fetchData();
},[fetchData]);
const isOnScreen = useOnScreen(elementRef, refetch);
or simply:
const isOnScreen = useOnScreen(elementRef, fetchData);
If fetchData changes reference for some reason, you might want to use the following instead:
const refetch= useRef(fetchData);
const isOnScreen = useOnScreen(elementRef, refetch);
Remember that useOnScreen has to call it like onIntersect.current()
In InboxList component, what we are saying by this code
if (isOnScreen) {
fetchData();
}
is that, every time InboxList renders, if waypoint is on screen, then initiate the fetch, regardless of whether previous fetch is still in progress.
Note that InboxList could get re-rendered, possibly multiple times, while the fetch is going on, due to many reasons e.g. parent component re-rendering. Every re-rendering will initiate new fetch as long as waypoint is on screen.
To prevent this, we need to keep track of ongoing fetch, something like typical isLoading state variable. Then initiate new fetch only if isLoading === false && isOnScreen.
Alternatively, if it is guaranteed that every fetch will push the waypoint off screen, then we can initiate the fetch only when waypoint is coming on screen, i.e. isOnScreen is changing to true from false :
useEffect(() => {
if (isOnScreen) {
fetchData();
}
}, [isOnScreen]);
However, this will not function correctly if our assumption, that the waypoint goes out of screen on every fetch, does not hold good. This could happen because
pageSize of fetch small and display area can accommodate more
elements
data received from a fetch is getting filtered out due to
client side filtering e.g. searchTerm.
As my assumption. Also you can try this way.
const observeRef = useRef(null);
const [isOnScreen, setIsOnScreen] = useState(false);
const [prevY, setPrevY] = useState(0);
useEffect(()=>{
fetchData();
var option = {
root : null,
rootmargin : "0px",
threshold : 1.0 };
const observer = new IntersectionObserver(
handleObserver(),
option
);
const handleObserver = (entities, observer) => {
const y = observeRef.current.boundingClientRect.y;
if (prevY > y) {
fetchData();
}
setPrevY(y);
}
},[prevY]);
In this case we not focus chat message. we only focus below the chat<div className="item element. when div element trigger by scroll bar the fetchData() calling again and again..
Explain :
In this case we need to use IntersectionObserver for read the element position. we need to pass two parameter for IntersectionObserver.
-first off all in the hanlderObserver you can see boundingClientRect.y. the boundingClientRect method read the element postion. In this case we need only y axis because use y.
when the scrollbar reach div element, y value changed. and then fetchData() is trigger again.
root : This is the root to use for the intersection. rootMargin : Just like a margin property, which is used to provide the margin value to the root either in pixel or in percent (%) . threshold : The number which is used to trigger the callback once the intersection’s area changes to be greater than or equal to the value we have provided in this example .
finally you can add loading status for loading data.
return (
<div className='conversations'>
{renderMsgList({ messages: messagesCopy })}
<div className='item' ref={observeRef} style={{ bottom: '10%', position: 'relative',backgroundColor:"blue",width:"5px",height:"5px" }} />
</div>
);
};
I hope its correct, i'm not sure. may it's helpful someone. thank you..

Why isn't my useState() hook setting a new state inside useEffect()?

Trying to detect if a <section> element is focused in viewport, I'm unable console.log a single true statement. I'm implementing a [isFocused, setIsFocused] hook for this.
This is my window:
I needed so when Section 2 is positioned at the top of the window, a single console.log(true) shows up. But this happens:
This is my implementation:
import React, { useEffect, useRef, useState } from "react";
const SectionII = (props) => {
const sectionRef = useRef();
const [isFocused, setIsFocused] = useState(false);
const handleScroll = () => {
const section = sectionRef.current;
const { y } = section.getBoundingClientRect();
if(!isFocused && y <= 0) {
setIsFocused(true);
console.log(isFocused, y);
}
};
useEffect(() => {
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
return (
<section id="mentorship" ref={sectionRef} style={{borderTop: "1px solid"}}>
<h1>Section 2</h1>
<button>Set hash</button>
</section>
);
};
export default SectionII;
Why wouldn't my state by updated to true with setIsFocused(true) inside if(!isFocused && y <= 0)?
Thanks so much for the insight. I'm really stuck.
When you're using any state management in react, you need to ensure that the change is set before attempting to access the new state value. For your example, you immediately console.log(isFocused, y) following your setState function (changes will only appear on the next DOM render). Rather, you should use a callback with the set state function, setIsFocused(true, () => console.log(isFocused, y)).

Testing mouse event listener added using ref in React functional component

Hi I have a functional component as shown below:
import React, { useRef, useEffect, useState } from 'react';
const SomeComponent = ({ prop1, ...otherProps}) => {
const divRef = useRef();
useEffect(() => {
divRef.current.addEventListener('mousedown', mouseDownFunc);
}, []);
const mouseDownFunc = () => {
document.addEventListener('mousemove', (el) => {
// call some parent function
});
}
return (
<div
className='test-div'
ref={ divRef }>
</div>
);
};
How do I test a react functional component wherein addEventListener is added using ref inside useEffect which when triggered calls mouseDownFunc.
I'm new to react jest testing, little confused on how to do it.
Testing this sort of component can be tricky, but using #testing-library/react I think I was able to come up with something useful.
I did have to make some changes to your component to expose the API a bit, and I also made some changes so that it stops listening to the events on mouseup which may not be the specific event you want.
Here's the modified component:
// MouseDownExample.js
import React, { useEffect, useState } from "react";
export default ({ onMouseMoveWhileDown }) => {
const [x, setX] = useState(null);
const [listening, setListening] = useState();
// Replaced with mouse move function, should make sure we're unlistening as well
useEffect(() => {
if (listening) {
const onMouseMove = (event) => {
// call some parent function
onMouseMoveWhileDown(event);
console.log(event.clientX);
// purely for testing purposes
setX(event.clientX);
};
const onMouseUp = (event) => {
// stop listening on mouse up
// - you should pick whatever event you want to stop listening
// - this is global so it also stops when the mouse is outside the box
setListening(false);
};
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
return () => {
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
};
}
}, [listening, onMouseMoveWhileDown]);
return (
<div
style={{
backgroundColor: "red",
width: 200,
height: 200
}}
className="test-div"
onMouseDown={() => {
// moved this inline, so no ref
setListening(true);
}}
>
X Position: {x}
</div>
);
};
I called out in comments the main differences.
And here's an example test:
// MouseDownExample.test.js
import React from "react";
import { fireEvent, render } from "#testing-library/react";
import MouseDownExample from "./MouseDownExample";
it("shouldn't trigger onMouseMoveWhileDown when mouse isn't down", () => {
const onMouseMoveWhileDown = jest.fn();
const { container } = render(
<MouseDownExample onMouseMoveWhileDown={onMouseMoveWhileDown} />
);
// Note: normally I would use `screen.getByRole` but divs don't have a useful role
const subject = container.firstChild;
fireEvent.mouseMove(
document,
// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent
{
clientX: 200
}
);
// hasn't gone down yet
expect(onMouseMoveWhileDown).not.toHaveBeenCalled();
fireEvent.mouseDown(subject);
fireEvent.mouseUp(subject);
// went down then up before moving
fireEvent.mouseMove(document, {
clientX: 200
});
expect(onMouseMoveWhileDown).not.toHaveBeenCalled();
});
it("should trigger onMouseMoveWhileDown when mouse is down", () => {
const onMouseMoveWhileDown = jest.fn();
const { container } = render(
<MouseDownExample onMouseMoveWhileDown={onMouseMoveWhileDown} />
);
// Note: normally I would use `screen.getByRole` but divs don't have a useful role
const subject = container.firstChild;
fireEvent.mouseDown(subject);
fireEvent.mouseMove(document, {
clientX: 200
});
expect(onMouseMoveWhileDown).toHaveBeenCalledWith(
expect.objectContaining({ clientX: 200 })
);
});
What's happening here, is we're rendering the component, then firing events to ensure the onMouseMoveWhileDown function prop is called when we expect.
We have to do expect.objectContaining rather than just the object because it's called with a MouseEvent which contains other properties.
Another test we might want to add is an unmount test to ensure the listeners are no longer triggering events.
You can look at/experiment with this Code Sandbox with this component and the tests. Hope this helps đź‘Ť

updating current time every second in react without rendering

Many of my components in a react native app require to know what the current time is every second. This way I can show updated real-time information.
I created a simple functionality to set the state with new Date(), but whenever I set the state, the component re-renders, which is a waste my case.
Here is what I have:
...
export default function App() {
const [currentDateTime, setCurrentDateTime] = useState(() => new Date().toLocaleString());
useEffect(() => {
const secondsTimer = setInterval(() => {
setCurrentDateTime(new Date().toLocaleString());
}, 1000);
return () => clearInterval(secondsTimer);
}, [setCurrentDateTime]);
console.log('RENDERING');
<Text>{currentDateTime}</Text>
...
I can see the console logs RENDERING every second.
Is there a way to avoid this rerendering and still update currentDateTime
Consider using shouldComponentUpdate lifecycle method; It's purpose is for preventing unnecessary renders. Add this method and tell your component not to update if this particular part of your state changes. As an example, you might add this shouldComponentUpdate() that rejects updates that are more than
// Example logic for only re-rendering every 5 seconds; Adapt as needed.
shouldComponentUpdate(nextProps, nextState) {
if (this.lastUpdatedTimeInSeconds+5 >= nextState.timeinseconds) {
return false;
}
this.lastUpdatedTimeInSeconds = nextState.timeinseconds
return true;
}
Further Reading: https://developmentarc.gitbooks.io/react-indepth/content/life_cycle/update/using_should_component_update.html
If I understand what you're saying, you want to update the DOM without triggering React's lifecycle. This is possible using refs (see React.useRef):
import * as React from "react";
import "./styles.css";
export default function App() {
const dateTimeRef = React.useRef<HTMLSpanElement>(null);
console.log("RENDERING");
React.useEffect(() => {
const secondsTimer = setInterval(() => {
if (dateTimeRef.current) {
dateTimeRef.current.innerText = new Date().toLocaleString()
}
}, 1000);
return () => clearInterval(secondsTimer);
}, []);
return <span ref={dateTimeRef} />;
}
See working demo - https://codesandbox.io/s/nice-snow-kt500?file=/src/App.tsx
Update 1
If you want to use a component such as Text, then the component will have to forward the ref to the dom, like here:
import * as React from "react";
import "./styles.css";
const Text = React.forwardRef<HTMLSpanElement>((props: any, ref) => {
console.log("RENDERING TEXT")
return <span ref={ref}></span>
});
export default function App() {
const dateTimeRef = React.useRef<HTMLSpanElement>(null);
console.log("RENDERING APP");
React.useEffect(() => {
const secondsTimer = setInterval(() => {
if (dateTimeRef.current) {
dateTimeRef.current.innerText = new Date().toLocaleString();
}
}, 1000);
return () => clearInterval(secondsTimer);
}, []);
return <Text ref={dateTimeRef} />;
}
See working demo - https://codesandbox.io/s/jolly-moon-9zsh2?file=/src/App.tsx
Eliya Cohen's answer was conceptually correct. To avoid re-rendering, we cannot use state with an interval. We need to reference the element. Unfortunately, I wasn't able to adopt Eliya's React code to React Native in the same manner, so I did some more digging and found docs on directly manipulating React Native components.
In short, you can manipulate built in RN components' PROPS and avoid re-rendering by not changing the state.
Since the <Text> component doesn't set its value with a prop, such as <Text text="my text" />, we are not able to use this method to update it. But what does work is updating the value of a TextInput since its set with the value prop. All we need to do to make the <TextInput> behave like a <Text> is to set its prop editable to false, and of course avoid default styling of it that would make it look like an input.
Here is my solution. If someone has a better one, please do propose it.
import React, { useEffect } from 'react';
import { TextInput } from 'react-native';
const Timer: React.FC = () => {
updateTime = (currentTime) => {
time.setNativeProps({ text: currentTime });
};
useEffect(() => {
const secondsTimer = setInterval(() => {
updateTime(new Date().toLocaleString());
}, 1000);
return () => clearInterval(secondsTimer);
}, []);
return <TextInput ref={(component) => (time = component)} editable={false} />;
};
export default Timer;
I also tried this and this is what that worked for me after a few attempts with Typescript.
const timeTextInput = useRef<TextInput>(null);
useEffect(()=>{
const timer = setInterval(() => {
timeTextInput.current?.setNativeProps({ text: new Date().toLocaleString() });
}, 1000);
return () => clearInterval(timer);
}, []);
Hope this helps someone in the future.

useState not setting after initial setting

I have a functional component that is using useState and uses the #react-google-maps/api component. I have a map that uses an onLoad function to initalize a custom control on the map (with a click event). I then set state within this click event. It works the first time, but every time after that doesn't toggle the value.
Function component:
import React, { useCallback } from 'react';
import { GoogleMap, LoadScript } from '#react-google-maps/api';
export default function MyMap(props) {
const [radiusDrawMode, setRadiusDrawMode] = React.useState(false);
const toggleRadiusDrawMode = useCallback((map) => {
map.setOptions({ draggableCursor: (!radiusDrawMode) ? 'crosshair' : 'grab' });
setRadiusDrawMode(!radiusDrawMode);
}, [setRadiusDrawMode, radiusDrawMode]); // Tried different dependencies.. nothing worked
const mapInit = useCallback((map) => {
var radiusDiv = document.createElement('div');
radiusDiv.index = 1;
var radiusButton = document.createElement('div');
radiusDiv.appendChild(radiusButton);
var radiusText = document.createElement('div');
radiusText.innerHTML = 'Radius';
radiusButton.appendChild(radiusText);
radiusButton.addEventListener('click', () => toggleRadiusDrawMode(map));
map.controls[window.google.maps.ControlPosition.RIGHT_TOP].push(radiusDiv);
}, [toggleRadiusDrawMode, radiusDrawMode, setRadiusDrawMode]); // Tried different dependencies.. nothing worked
return (
<LoadScript id="script-loader" googleMapsApiKey="GOOGLE_API_KEY">
<div className="google-map">
<GoogleMap id='google-map'
onLoad={(map) => mapInit(map)}>
</GoogleMap>
</div>
</LoadScript>
);
}
The first time the user presses the button on the map, it setss the radiusDrawMode to true and sets the correct cursor for the map (crosshair). Every click of the button after does not update radiusDrawMode and it stays in the true state.
I appreciate any help.
My guess is that it's a cache issue with useCallback. Try removing the useCallbacks to test without that optimization. If it works, you'll know for sure, and then you can double check what should be memoized and what maybe should not be.
I'd start by removing the one from toggleRadiusDrawMode:
const toggleRadiusDrawMode = map => {
map.setOptions({ draggableCursor: (!radiusDrawMode) ? 'crosshair' : 'grab' });
setRadiusDrawMode(!radiusDrawMode);
};
Also, can you access the state of the map options (the ones that you're setting with map.setOptions)? If so, it might be worth using the actual state of the map's option rather than creating your own internal state to track the same thing. Something like (I'm not positive that it would be map.options):
const toggleRadiusDrawMode = map => {
const { draggableCursor } = map.options;
map.setOptions({
draggableCursor: draggableCursor === 'grab' ? 'crosshair' : 'grab'
});
};
Also, I doubt this is the issue, but it looks like you're missing a closing bracket on the <GoogleMap> element? (Also, you might not need to create the intermediary function between onLoad and mapInit, and can probably pass mapInit directly to the onLoad.)
<GoogleMap id='google-map'
onLoad={mapInit}>
This is the solution I ended up using to solve this problem.
I basically had to switch out using a useState(false) for setRef(false). Then set up a useEffect to listen to changes on the ref, and in the actual toggleRadiusDraw I set the reference value which fires the useEffect to set the actual ref value.
import React, { useCallback, useRef } from 'react';
import { GoogleMap, LoadScript } from '#react-google-maps/api';
export default function MyMap(props) {
const radiusDrawMode = useRef(false);
let currentRadiusDrawMode = radiusDrawMode.current;
useEffect(() => {
radiusDrawMode.current = !radiusDrawMode;
});
const toggleRadiusDrawMode = (map) => {
map.setOptions({ draggableCursor: (!currentRadiusDrawMode) ? 'crosshair' : 'grab' });
currentRadiusDrawMode = !currentRadiusDrawMode;
};
const mapInit = (map) => {
var radiusDiv = document.createElement('div');
radiusDiv.index = 1;
var radiusButton = document.createElement('div');
radiusDiv.appendChild(radiusButton);
var radiusText = document.createElement('div');
radiusText.innerHTML = 'Radius';
radiusButton.appendChild(radiusText);
radiusButton.addEventListener('click', () => toggleRadiusDrawMode(map));
map.controls[window.google.maps.ControlPosition.RIGHT_TOP].push(radiusDiv);
});
return (
<LoadScript id="script-loader" googleMapsApiKey="GOOGLE_API_KEY">
<div className="google-map">
<GoogleMap id='google-map'
onLoad={(map) => mapInit(map)}>
</GoogleMap>
</div>
</LoadScript>
);
}
Not sure if this is the best way to handle this, but hope it helps someone else in the future.

Resources