Zooming in React ThreeJS scene with zoombar and wheel scroll - reactjs

I am designing a 3D scene by react-three fiber, and I want to be able to zoom in on the scene via multiple sources (I'm using a zoom Bar and mouse wheel scrolling for zooming now, but other sources may be added in the future like buttons for zoom in and out). To do so, I am using a global state ZoomAmount variable which is a DOM element, and in the onchange callback of the bar, this variable is changed:
const handleChange = () => {
setZoomAmount(refZoomBar.current.value)
}
then I made a hook for zoomAmount state using the react useEffect, so I can change camera.position.z (changing this makes zoom effect)
useEffect(() => {
camera.position.z = ZoomAmount
}, [ZoomAmount])
The problem is that I want the value of the zoombar to change when I zoom in and out using another input (in this case the mouse wheel scroll,) but when I try to implement this feature in the same way by using hooks, the code enters a loop since these elements are both affected by the same value.
How can I do this without facing this issue?
I have put a running demo of my problem here:
https://codesandbox.io/s/zooming-using-multiple-inputs-lub391
I wasn't able to fix this issue but here is what I have tried:
I implemented two hooks
First, a controls eventlistener to set the ZoomAmount whenever camera changed and second, a hook for zoombar to listen to ZoomAmount value change
controls.current?.addEventListener('change', () => {
ZoomAmount = camera.position.z
})
useEffect(()=>{
refZoomBar.current.value = ZoomAmount
},[ZoomAmount])
the above codes are not included in the Sandbox code since they make the program misbehave
This is where the infinite loop starts since ZoomAmount gets changed if the value of camera.position.z is changed and then camera.position.z gets changed again because it is hooked to ZoomAmount.
I also tried using two different states for ZoomAmount but it only makes the loop bigger

Related

React threefibre zooming from zoombar and scroll

I have a 3d scene built with react threefibre and I'm able to zoom in and out with both mousewheel and a DOM eleemnt zoombar in this scene.
the demo of the scene can be seen here:
https://codesandbox.io/s/zooming-using-multiple-inputs-lub391
right now, when you zoom with mousewheel, zoombar stays the same, but I want the it change dependant on the mousewheel.
note that I don't want to use mouse wheel event because I may have more sources that also change the zoom and I want the zoombar to be dependant on them too. so the proper way is to add an event listener on camera movement. I wasn't able to do that because whatever I do makes an endless loop.
Perhaps you could use an EventListener on the identified Canvas that listens for mousewheel events like so:
useEffect(() => {
document.getElementById('map-canvas')?.addEventListener('mousewheel', onMouseWheel, false)
return () => {
document.getElementById('map-canvas')?.removeEventListener('mousewheel', onMouseWheel, false)
}
}, [])
return (
<Canvas id="map-canvas"></Canvas>
)
The onMouseWheel function adds the desired value to the previously created context.

Framer Motion animation repeats on state change

I am building a task manager with React and Framer Motion. I'm animating a slide in transition for a react Portal / modal. The portal has a form in it with multiple input text fields in it, each with their own onChange handlers. However, whenever the onChange handler is called, the animation replays itself. I'm not sure what is the issue.
I've tried to add a repeat value of 0 to the transitions, but there seems to be no change. I'm pretty new to framer motion, so please let me what other details I need to provide.
If you are using animate prop, the component will animate on each render, this is useful only for simple cases, where the component typically renders once on the page. But in your case, this prop will not work since the input field re-renders on each change.
For your use case, it's better to use useAnimation() hook and pass the animationControl as variant prop and fire it manually on the first render only.
useEffect(() => {
if (firstRender) {
animationControl.start();
}
}, [])

React - Render component based on the position of other components

I want to draw a line between two boxes. So I have two components for the boxes, and I want a third component to render as a line between these two components. I don't know the positions and dimensions of the two boxes until they have completed rendering, since it will depend on the rendering of other components. In order to draw the line, I need the position of both boxes relative to a common parent element. (The line is drawn with absolute positioning though, so will not affect the boxes.)
So the question is, how do I do this in React? If I understand React correctly, information is passed downwards in the tree, but can flow upwards through callbacks. But the components just pass react elements off to the framework and the actual rendering happens in the background. So how do I access the result of rendering, e.g. the final coordinates of the DOM element?
Furthermore, if I pass this information (the coordinates of the boxes) upwards to a common ancestor and store it in state, don't I risk an infinite loop where updating these coordinates will cause the components to render again?
(I'm using function components if that matters.)
Edit: I'm not asking for a graph-rending library or anything like that. The boxes and line is just an example. I'm asking how to have a React component render based on the DOM coordinates of other rendered React components.
You could use the useRef hook:
import {useRef, useLayoutEffect} from 'react'
const Page = () => {
const componentRef = useRef()
useLayoutEffect(()=> {
console.log(componentRef.current.clientHeight). // height of ref element
console.log(componentRef.current.clientWidth) // width of ref element
console.log(componentRef.current.offsetTop). // px to top of window from ref element
console.log(componentRef.current.offsetLeft) // px to left of window from ref element
)}
return (
<div ref={componentRef}>
Component
</div>
)
}
with offsetTop and offsetLeft you can get the position since offsetTop = y axis and offsetLeft = x axis

onDragEnd not fired when viewport is left

I have found the layer remains in dragmove state when a it is dragged out of the viewport and the mouse button is released. Did you experience this kind of issue?
Have a look over here: https://codesandbox.io/s/llxq3yv829?file=/index.js
Drag the red rectangle out of the viewport and it remains sticky to the cursor.
Thank you!
Set a global event for mouseleave
Use useState to set a state at app level. This can be passed down to components relying on the mouse being in the viewport. Then trigger something to "release" the dragged object.
document.addEventListener("mouseleave", (event) => {
setState({inViewport: false})
}
In the example pass the state to the component.

Hooks API - Avoid state overwrite when setting state after await statement

Let's say we have a tile grid with the follow behavior:
There's a button for adding a tile. The tile will decide if it should be a red or blue tile after some time thinking (1-3secs). The tile will be green while it's thinking what color it should be.
Clicking a title will remove the tile from the grid
Let tiles be an array of tile colors which will serve as the state of the tile grid and setTiles as the setter of that state. Given the grid behavior, setTiles should be called during these events:
on click of a tile. The clicked tile should be removed from tiles.
on click of the add button. A green tile must be added to tiles.
after a tile decides what it's color is. The corresponding green value in tiles must be changed to red or green.
I've prepared a codepen of a simple implementation of the grid detailed above. The issue with this is that calls to setTiles after an await statement overwrites state changes that happened between the start and end of the asynchronous statement. I think this is because the function only has reference to the state value before starting the async operation.
What's the best architecture (component changes, callback passing, tile data structure), using SFCs and the Hooks API, to avoid the unintended overwrites and, ultimately, the tile grid having an updated array of tile data at all times?
Solved it by using useReducer. Wasn't aware that with useReducer, you have a "synchronised" state for all dispatched actions. The state overwrite wasn't an issue with the seemingly-central state with reducers. Working code here.

Resources