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

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

Related

onMouseOver keeps calling when data changes

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

What is the proper way to smoothly scale an element to nothing and then back to its original size

This is how I do it in React:
const animate_boxes = () => {
inner_ref.current.style.transform = "scale(0)";
setTimeout(() => {
if (inner_ref && inner_ref.current) {
inner_ref.current.style.transform = "scale(1)";
}
}, 200);
};
For some reason, it is not dependable. the setTimeout may not always get run. Still haven't figured out why yet. But If there is an alternative way to do it, then please write a solution.
thanks
May I suggest the react-spring library?
Use framer motion library with scale property passed in as an array of values
eg :
import {motion} from 'framer-motion'
const Component = () => {
return (
<motion.div
initial={{scale: 1}}
animate={{scale: [0.1, 1]}}
>
hi
</motion.div>
)
}
Refer this docs for more examples - Docs

When is it safe to call `measureInWindow`?

I'm trying to figure out at what point in a component lifecycle I can call measureInWindow on a view and be guaranteed to get the right values back. For example:
const Measure = () => {
const ref = React.useRef();
React.useLayoutEffect(() => {
ref.current.measureInWindow((x, y) => {
// y is 0
})
});
return <View ref={ref} />;
};
I get 0 for y in the above example, rather than 88 which is the right value because the navigation bar is that tall. If I put the measureInWindow call inside a setTimeout with 0ms then I get the correct value back.
I have also tried using using useEffect instead of useLayoutEffect and I get the same result.
EDIT
A comment on this post got me looking at the onLayout prop for View. My initial tests indicate that I can call measureInWindow successfully there. However, I'd still like to find some concrete documentation beyond just my cursory testing.
<View
ref={ref}
onLayout={e => {
ref.current.measureInWindow((x, y) => {
console.log('onLayout', y);
});
}}
/>

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