onMouseOver keeps calling when data changes - javascript

I have an application where I draw up to date locations on a map. These locations update about every second. I'm trying to have it so that I can hover over the locations, which are svg circles but onMouseOver keeps calling every time my useEffect updates the data and the locations on the map update. How can I fix this problem.
So basically I have an svg, which has a list of the following elemnts in:
const location = <circle
key = {'serialNumber'}
cx = {x}
cy = {y}
r = {4}
className="drone"
onMouseOver = {displayPilotName('name')}
/>
And I draw that on a map.
My useEffect works as following:
useEffect(() => {
const interval = setInterval(() => {
droneService.getAll().then(droneInfo =>
setDrones(droneInfo))
}, 500)
return () => clearInterval(interval)
}, [])
I think the problem is because these two but might be cause by something else, but if I changed The interval to say 3 seconds it only called onMouseOver every 3 seconds. Also hover in css works fine, but I need to call an function from the hover so it doesn't work for this.

You are calling that function every render and passing whatever it returns... you should instead pass callback function
const location = <circle
key = {'serialNumber'}
cx = {x}
cy = {y}
r = {4}
className="drone"
onMouseOver = {()=>displayPilotName('name')}
/>
this will be triggered only on mouseOver

Related

a function that supposed to return a different value each time does not work in react

I am working on my portfolio, but when I reach the experiences page I tried to create a title that has it's middle part change every second, and value of it must come from an already set up array,
but when I run the code it always return the first string of the array,
can anyone please fix this problem for me ?
const projectsTitleKeyWords = ['responsible', 'meaningful', 'beautiful']
let titlep2 = 'test'
let index = 0
const change = () => {
titlep2 = projectsTitleKeyWords[index]
index = ++index % projectsTitleKeyWords.length
setTimeout(change, 1000)
}
change()
console.log(titlep2)
const titlep1 = 'I creat '
const titlep1Array = titlep1.split('')
let titlep2Array = titlep2.split('')
const titlep3 = ' projects'
const titlep3Array = titlep3.split('')
the value of titlep2Array will be received by
<AnimatedLetters
letterClass={letterClass}
strArray={titlep2Array}
idx={15}
id='to-change'
/>
In-order to reflect UI changes in React, a component must re-render.
A React component re-renders in 1 of 2 scenarios:
Whenever there's a change in the value of a local state.
Whenever any of it's parent components re-render.
Therefor, since changes in the UI are only reflected upon a re-render, we should manage a local state that would be responsible for this behavior.
With functional components in React, this can be achieved via the useState hook.
In your case, we can simply make titlep2 a state, instead of a regular variable.
Example:
const [titlep2, setTitlep2] = useState('')
const change = () => {
setTitlep2(projectsTitleKeyWords[index])
index = ++index % projectsTitleKeyWords.length
setTimeout(change, 1000)
}
<AnimatedLetters
letterClass={letterClass}
strArray={titlep2.split('')}
idx={15}
id='to-change'
/>
Note: since this function now updates the state, we can't call it the way you did in your example, since it will run every time the component re-renders, making the component re-render indefinitely due to the change in state.
Therefor, we can use the useEffect hook in-order to allow it to run only once on the initial render.
Example:
const change = () => {
setTitlep2(projectsTitleKeyWords[index])
index = ++index % projectsTitleKeyWords.length
setTimeout(change, 1000)
}
useEffect(() => {
change()
}, [])
Furthermore, if there are any other variables that should reflect changes in the UI, they can be convert to states as well.
for that try using the setInterval() instead of setTimeout().
You are trying to make the text change after a specific interval in this case 1 second.
You should also consider doing that will CSS animations, it seems this is overkill.
const change = () => {
titlep2 = projectsTitleKeyWords[index]
index = ++index % projectsTitleKeyWords.length
console.log(titlep2)
setInterval(change, 1000)
}
change()

How to pass React state variable into setInterval function

I created a drag and drop menu and so far everything works, however, I have run into a small coding problem for my last step. I need to get the menu of items to automatically scroll when the dragged item is towards the bottom of the container (or top). I decided to structure it so that when the cursor dragging the item falls below a specific range towards the bottom, a scroll function will trigger and continually scroll down at a pace I can set. In order to achieve this I set up a setInterval function and will continually call a scrollBy function.
My issue is that In order to stop the setInterval and call clearInterval, I need to know when the position of the cursor is no longer in that range. Every time the function is called in setInterval, it takes a snapshot of the current state variables. Instead of updating the state of those variables (yAxis position) within the function, it is stuck on the first value it gets.
The yPoisiton is obtained with the window.event.clientY from another component.
document.onmousemove = (e) => {
if (dragging.state) {
var newYPos = window.event.clientY;
setYPos(newYPos);
handleScroll(newYPos, dragging.state);
The rest of the code goes as follows:
const menuContainer = useRef();
const [scrolling, setScrolling] = useState(false);
const [yPos, setYPos] = useState(null);
const [dragging, setDragging] = useState({
state: false,
index: null,
y: null,
height: null,
});
function continueScrolling() {
console.log("continueScrolling function");
console.log(yPos); // Same value even after updating via setYPos)
const date = new Date();
console.log(date.toLocaleTimeString()); //Different values in accordance to the time
}
console.log("Index.js yPos");
console.log(yPos); // Different values every render when dragging
const handleScroll = (draggingYPos, isDragging) => { // This is triggered onMouseMove in another component
if (
draggingYPos >
menuContainer.current.getBoundingClientRect().bottom - 100 &&
isDragging
) {
setScrolling(true); // On the first cycle scrolling == false and executes the setInterval function. Each subsequent call is then skipped.
if (scrolling) {
return;
} else {
var intr = setInterval(function () {
continueScrolling(); // Different method of testing and trying to get the current yPos in the function
//Commented but shows a bit of what I'm trying to accomplish
// if (
// !(
// yPos >
// menuContainer.current.getBoundingClientRect().bottom - 100 &&
// dragging.state
// )
// )
// clearInterval(intr);
}, 1000);
Please let me know if there is more information you need or anything else I can clarify.
This is a temporary solution I found to my problem, however, I would still like to know a more proper and robust solution if anyone has one, as well as information as to the scope of functions, React state, and how to pass them between one another.
I realize that it is not recommended to mutate states directly, however, this is my only current working solution. If I create a temporary value and pass in the setInterval, it will call setInterval a second time and will still be active after clearing it.
If I declare a global const variable, I cannot reassign the variable with setInterval. When I declared a global variable with var, the setInterval function continued even after I cleared it.
Adjusted code:
const [scroll, setScroll] = useState(null);
const handleScroll = (draggingYPos, isDragging) => {
if (
draggingYPos >
menuContainer.current.getBoundingClientRect().bottom - 100 &&
isDragging
) {
setScrolling(true);
if (scrolling) {
return;
} else {
var count = 0;
if (
!(
yPos > menuContainer.current.getBoundingClientRect().bottom - 100 &&
dragging.state
)
) {
setScroll(
setInterval(function () {
count += 1;
console.log(count);
}, 1000)
);
}
}
} else {
console.log("Not Scrolling");
setScroll(clearInterval(scroll));
setScrolling(false);
}
};

react-spring animation only working on click, not on hover

I have a basic rotating cube I've made with react-three-fiber, and onPointerOver (also tried onPointerEnter) I want to smoothly rotate it to its starting position via useSpring. However, not only does it not do so when I hover, but it will only do so onClick. I have a totally different function that executes onClick -- and if I disabled that prop then the rotation reset fails altogether.
I've got a pretty simple set up in my Box component:
export const Box = () => {
const [active, setActive] = useState(false);
const boxRef = useRef<Mesh>();
const starterRotation = [0, 1, 1];
useFrame(() => {
if (boxRef.current) {
boxRef.current.rotation.x += 0.01;
boxRef.current.rotation.y -= 0.01;
}
});
const [resetRotation, setSpring] = useSpring(() => ({
rotation: starterRotation
})); // trying to use the `set` syntax to call the useSpring and smoothly animate to a certain rotation.
const springs = useSpring({ scale: active ? 1.5 : 1, config: config.wobbly });
return (
<animated.mesh
// #ts-ignore
rotation={resetRotation.rotation}
scale={springs.scale}
onClick={() => setActive(!active)}
onPointerOver={() => setSpring.start()}
ref={boxRef}
>
<boxGeometry args={[2, 1, 2]} />
<meshStandardMaterial color="royalblue" />
</animated.mesh>
);
};
Here's a live example: https://codesandbox.io/s/react-spring-rotating-cube-np4v6
From what I can tell in their docs this is the correct way. I also saw some github issues discussions on it, and there seemed to be some concern about the second argument of the useSpring deconstruction not working properly, but the rotation works for me -- it's jut the triggering event that won't work.
It does work, the issue is you're still updating the rotation based on the ref in a useFrame call so that's updated every frame. Which will override the animation value.
The second issue is that if you stoped the useFrame from animating rotation it won't work because the spring's internal value will be set to [0,1,1] but that's what you want it to animate to.
This is a good opportunity to use from and to props of useSpring, what I would do is use a ref to stop the useFrame animation and then use the ref to get the current values of the rotation to use as from in the springSet.start function and then to which is the starterRotation value you've declared.
You can see this in effect here – https://codesandbox.io/s/react-spring-rotating-cube-forked-1j02g?file=/src/components/Box.tsx

Issue accessing state array from inside animationFrame with react

I am trying to update some elements on scroll by using animationFrame. I want to add an easing effect so I would like the elements to update their positions by the eased value. I figured the best way to do this would be to store all of the values in an array and update them accordingly. When each element is mounted I am sending them to a context element that adds them to the state value array.
My issue is that I cannot access the array from inside the animating function. It is available outside of the animating function but not inside. I am assuming that the animation is starting before the array is being populated but I have tried to stop and restart the animation when the blocks array changes with useEffect but to no avail.
Here is a codesandbox of the issue Example Of Issue
In the sandbox you can see in the animate() function in the ScrollContainer component I am console logging the blocks array and then after the function I am logging the same array. When you scroll the array does not log the available blocks only an empty array. But the available blocks are being logged correctly under this function.
const animate = () => {
const diff = yScroll - yCurrent;
const delta = Math.abs(diff) < 0.1 ? 0 : diff * ease;
if (delta) {
yCurrent += delta;
yCurrent = parseFloat(yCurrent.toFixed(2));
animationFrame = requestAnimationFrame(animate);
} else {
cancelAnimation();
}
console.log("Animating Blocks", blocks);
};
console.log("Available Blocks", blocks);
const addBlock = block => {
setBlocks(prev => {
return [...prev, block];
});
};
and here is how I am starting the animation
const startAnimation = () => {
if (!animationFrame) {
animationFrame = requestAnimationFrame(animate);
}
};
useEffect(() => startAnimation(), []);
Thanks.
I played with your example. It seems to me, that the problem is in the useEffect. If you add empty dependency to it, then it runs only once after the first render. There will be a second render when blocks state updates, but for the useEffect only the first state is visible because it runs only once and it uses the startAnimation with stale closure. This version of startAnimation uses the first version of the animation with the original state.
Your initial problem is solved if you add blocks to the useEffect as a dependency.
useEffect(() => {
yScroll = window.scrollY || window.pageYOffset;
yCurrent = yScroll;
startAnimation();
window.addEventListener("scroll", updateScroll);
return () => {
window.removeEventListener("scroll", updateScroll);
};
}, [blocks]);
I tried adding the animation, but is is quite choppy to me. I'm interested in your final solution. This is mime: https://codesandbox.io/s/scrolling-animation-frame-array-issue-d05wz
I use higher level animation libraries like react-spring. You can consider using something like this. I think it is much easier to use.
https://codesandbox.io/s/staging-water-sykyj

React hook saved state in Event Listener wrong?

I have points and a function to update points:
const [points, setPoints] = useState([])
const updatePoint = (updatedPoint) => {
setPoints(points.map(point => (point.id === updatedPoint.id ? updatedPoint : point)))
}
I've added a Listener to the marker:
window.google.maps.event.addListener(marker, 'dragend',
function (markerLocal) {
console.log(getPoints)
}
)
If I click on first marker after I've created it, it shows me 1 point in the console.
If I create and click second it shows me 2 points, So it saves the state inside of the listener. However, after the second save, the sum of points for the first marker doesn't change(when it gets dragged). Is this the right behaviour? How can I get two points for the first marker? Whenever I try to update my point list and click the 1st marker, it gives me only one point - that's wrong.
Try adding useEffect with points dependency and set addEventListener for google maps. An issue here is the scope that the initial addEventListener is working on. In regular JS you would always have the current value in the scope, however, in hooks, your code will reference stale values from previous renders. In your case, you are referencing the values your points state was at the time of attaching the event to Google Maps. The cleanest solution here, in my opinion, is the Leftium's one, but you can also try this one for 'academic' purposes:
const onGoogleMapsMarkerDragend = () => {
console.log(points);
}
useEffect(() => {
const dragendListener = window.google.maps.event.addListener(marker, 'dragend', onGoogleMapsMarkerDragend);
return () => {
window.google.maps.event.removeListener(dragendListener );
}
}, [points];
Not sure if removeListener is valid - check with the documentation
This is probably because the handler uses the points at the time of declaration, which might be updated before it is invoked. My solution to such issues was to define it as a ref:
const points = useRef([]);
// This means that instead of `setPoints` you will do points.current = newValue
const updatePoint = (updatePoint) => {
points.current = points.current.map(...);
}
If a previous state is used to calculate the new state, as in your example, you should pass setPoints() a function of the old state instead of the new value directly:
const updatePoint = (updatedPoint) => {
setPoints((prevPoints) =>
prevPoints.map(point => (point.id === updatedPoint.id ? updatedPoint : point)
))
}
Relevant section from Hooks API Reference: Functional updates for useState

Categories

Resources