How to use useBounds on a DOM click event in three fiber?

function ZoomBack(){
	const [close, setclose] = useState(false)
	const api = useBounds();
	useEffect(() => {
		api.refresh().fit(); // just zoom back.. focus on whole scene
	}, [close])
 
	return (
		<h1 onClick={()=>{setclose(true)}}>
			Zoomback
	  </h1>
	)
}

How can I use the useBounds outside of the canvas scene structure? I just want to have a DOM element, a button which controls the useBounds…

Why I want this: When I use the

then each of the elements in group are zoomable and when I click outside of one of those elements, it zooms back. Exactyl the zoomback functionality I need it on a click event of a DOM element.

here is the SelectToZoom component:


function SelectToZoom({ children }) {
	const api = useBounds();
	return (
	  <group onClick={(e) => (e.stopPropagation(), e.delta <= 2 && api.refresh(e.object).fit(), console.log(e.object))} onPointerMissed={(e) => e.button === 0 && api.refresh().fit()}>
		{children}
	  </group>
	)
  }

How can I extract the onPointerMissed function and use it somewhere else?

Here is the example:

usebounds is contextual to its bounds provider, outside of that it can’t function. in my opinion reaching internals upwards in general is an anti pattern. an application should always flow top down unidirectionally.

a state model should contain simple, logical, serialisable information, a [x, y, z] vector that points towards where you want to zoom, or the id of the object that’s supposed to be in focus (better than the vector imo), etc. any component can now react to that. once you start to always divide the app between state and view everything will become easier.

ps this is a good representation of it: Shoe configurator - CodeSandbox

in the beginning i struggled, i was trying to reach the objects that needed to change color up just ending up with spaghetti. i stopped and chose a global state model, all problems vanished.

2 Likes

thank you, I understand that you say, I should have something like a state management on top on everything (you used valtio for that). But I still don’t get it how I can pass the useBounds context to valtios proxy so that I can access it from elsewhere. I tried something like this:

const state = proxy({
  current: null,
  api: null,
})

export default function App() {
  const api = useBounds()
  const snap = useSnapshot(state)
  state.api = api

return ( <ZoomBack></ZoomBack> .. 
...

function ZoomBack() {
  const snap = useSnapshot(state)
  return (
    <h1
      onClick={() => {
        snap.api.refresh().fit()
      }}>
      Zoomback
    </h1>
  )
}

but snap.api seems to be null - I think I am doing it wrong hehe

you dont need a state manager, could also just be a useState. whatever you use, i would not reach up a context into state, which is what i was saying - app flow should be top down, not down up. the state should just be an id, or a string: { active: “headphones” }. each item could listen to that:

function Model({ name }) {
  const bounds = useBounds()
  const current = useStore(state => state.current) // zustand, for instance ...
  useEffect(() => {
    // Each model will zoom onto itself if the global state indicates that it's in focus
    if (current === name) bounds.doThisDoThat(...)
  }, [current])
  ...

or you have a parent that manages its children:

function Parent({ children }) {
  const ref = useRef()
  const bounds = useBounds()
  const current = useStore(state => state.current)
  useEffect(() => {
    const target = ref.current.children.find(obj => obj.name === current)
    if (target) bounds.doThisDoThat(...)
    else bounds.zoomAll() // pseudo-code
  }, [current])
  return <group ref={ref}>{children}</group>

<Canvas>
  <Bounds>
  <Parent>
    <Model name="headphones" />
    <Model name="rocket" />
</Canvas>
<h1 onClick={() => setActive("rocket")}>
  ...

when you do this:

const snap = useSnapshot(state)
state.api = api

you reach up a canvas internal that just shouldn’t leak, i would consider it dirty even if you can make it work. the h1 should have no knowledge of “threejs”, “bounds” and other canvas stuff. the state model simply keeps a minimal description of app flow.

1 Like

I would say I have exactly what is in your second example. The parent which manages its children; buuut the problem in your example is exactly what I try to solve: you used in useEffect bounds.doThisDoThat() and exactly that is “sometimes” not working!

Cannot read properties of null (reading ‘updateWorldMatrix’)

Have a quick look in my example, it is set up the same way like your second example; somehow the call to useBounds().refresh().fit(); is only working when it’s called on a THREE object (for example onClick of the object) but not on the js code for example with a timeout or in useEffect.

I think I did it correctly, with managing the sate of the zoom status and on click of the DOM h1/button I change the sate and in the SelectToZoom I subscribe to the zoom status and react…